mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 13:08:55 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73af5d355d | ||
|
|
45a1a64db5 | ||
|
|
bc1c3c2197 | ||
|
|
79cfddfbee | ||
|
|
aa41bba68c | ||
|
|
86a90117e5 | ||
|
|
874a6b8de9 | ||
|
|
01c654cb68 | ||
|
|
5bc28623cb | ||
|
|
c1d7fcbb7f | ||
|
|
45eefd6c80 | ||
|
|
33e37eb375 | ||
|
|
4229b8ff85 | ||
|
|
91e4645315 | ||
|
|
425d8af73e | ||
|
|
0ce86fa8db | ||
|
|
ecc7db09a9 | ||
|
|
d7ddc2ab93 | ||
|
|
fd51422a36 | ||
|
|
cb36ca1b0d | ||
|
|
76a47e0dd0 | ||
|
|
70a754fb46 | ||
|
|
ffd79d5fe4 | ||
|
|
21a7b13524 | ||
|
|
52bdbe95c9 | ||
|
|
495dda3809 | ||
|
|
52e83922c0 | ||
|
|
6cbc5285cb | ||
|
|
33b9630ca5 | ||
|
|
9346f86f73 | ||
|
|
8d2d6627cf | ||
|
|
6f198b247c | ||
|
|
9fb95eff41 | ||
|
|
0b2b81fd23 | ||
|
|
63aecc598f | ||
|
|
51c8be6baf | ||
|
|
7aab8a3711 | ||
|
|
2789cee331 | ||
|
|
823704cf36 | ||
|
|
a7974f0f14 | ||
|
|
ed412bee35 | ||
|
|
6cee615f26 | ||
|
|
c4581b4d72 | ||
|
|
f6a568bcc1 | ||
|
|
bf6170e613 | ||
|
|
afcd79c0cc | ||
|
|
fdf0389936 | ||
|
|
5599c1694b | ||
|
|
dff90fae6f | ||
|
|
d33f679eae | ||
|
|
225e74a334 | ||
|
|
13e9a14907 | ||
|
|
92ea0a2f24 | ||
|
|
a1059650f6 | ||
|
|
a3cc221932 | ||
|
|
6732be1434 | ||
|
|
ad5cb056f0 | ||
|
|
d3c7388327 | ||
|
|
8e436ad920 | ||
|
|
ae806f734e | ||
|
|
ccfa454226 | ||
|
|
6716db1f62 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '7.0.0'
|
__version__ = '7.2.1'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
@@ -69,15 +69,21 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
||||||
# 6.7.0 - Handle new library in calibre.
|
# 6.7.0 - Handle new library in calibre.
|
||||||
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
||||||
# 6.8.1 - Kindle key fix for Mac OS X Big Syr
|
# 6.8.1 - Kindle key fix for Mac OS X Big Sur
|
||||||
# 7.0.0 - Switched to Python 3 for calibre 5.0. Thanks to all who comtibuted
|
# 7.0.0 - Switched to Python 3 for calibre 5.0. Thanks to all who contributed
|
||||||
|
# 7.0.1 - More Python 3 changes. Adobe PDF decryption should now work in some cases
|
||||||
|
# 7.0.2 - More Python 3 changes. Adobe PDF decryption should now work on PC too.
|
||||||
|
# 7.0.3 - More Python 3 changes. Integer division in ineptpdf.py
|
||||||
|
# 7.1.0 - Full release for calibre 5.x
|
||||||
|
# 7.2.0 - Update for latest KFX changes, and Python 3 Obok fixes.
|
||||||
|
# 7.2.1 - Whitespace!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt DRMed ebooks.
|
Decrypt DRMed ebooks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = "DeDRM"
|
PLUGIN_NAME = "DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (7, 0, 0)
|
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
||||||
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
||||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
@@ -421,7 +427,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
||||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||||
userkey = userkeyhex.decode('hex')
|
userkey = codecs.decode(userkeyhex,'hex')
|
||||||
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
||||||
of = self.temporary_file(".pdf")
|
of = self.temporary_file(".pdf")
|
||||||
|
|
||||||
@@ -467,7 +473,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
newkeys = []
|
newkeys = []
|
||||||
for keyvalue in defaultkeys:
|
for keyvalue in defaultkeys:
|
||||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
if codecs.encode(keyvalue,'hex') not in dedrmprefs['adeptkeys'].values():
|
||||||
newkeys.append(keyvalue)
|
newkeys.append(keyvalue)
|
||||||
|
|
||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
@@ -491,7 +497,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Store the new successful key in the defaults
|
# Store the new successful key in the defaults
|
||||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
try:
|
try:
|
||||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue,'hex'))
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
except:
|
except:
|
||||||
@@ -596,7 +602,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
of = self.temporary_file(".pmlz")
|
of = self.temporary_file(".pmlz")
|
||||||
|
|
||||||
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
||||||
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex'))
|
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, codecs.decode(userkey,'hex'))
|
||||||
|
|
||||||
of.close()
|
of.close()
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,9 @@ if iswindows:
|
|||||||
|
|
||||||
def _load_crypto_libcrypto():
|
def _load_crypto_libcrypto():
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
libcrypto = find_library('libeay32')
|
libcrypto = find_library('libcrypto-1_1')
|
||||||
|
if libcrypto is None:
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise ADEPTError('libcrypto not found')
|
raise ADEPTError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class ConfigWidget(QWidget):
|
|||||||
def load_resource(self, name):
|
def load_resource(self, name):
|
||||||
with ZipFile(self.plugin_path, 'r') as zf:
|
with ZipFile(self.plugin_path, 'r') as zf:
|
||||||
if name in zf.namelist():
|
if name in zf.namelist():
|
||||||
return zf.read(name)
|
return zf.read(name).decode('utf-8')
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ class DocParser(object):
|
|||||||
if (link > 0):
|
if (link > 0):
|
||||||
linktype = self.link_type[link-1]
|
linktype = self.link_type[link-1]
|
||||||
title = self.link_title[link-1]
|
title = self.link_title[link-1]
|
||||||
title = title.rstrip(b'. ')
|
title = title.rstrip(b'. ').decode('utf-8')
|
||||||
alt_title = parares[lstart:]
|
alt_title = parares[lstart:]
|
||||||
alt_title = alt_title.strip()
|
alt_title = alt_title.strip()
|
||||||
# now strip off the actual printed page number
|
# now strip off the actual printed page number
|
||||||
@@ -770,10 +770,10 @@ class DocParser(object):
|
|||||||
first_para_continued = False
|
first_para_continued = False
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||||
if not pclass:
|
if not pclass:
|
||||||
if orig_regtype.endswith(b'.right') : pclass = 'cl-right'
|
if orig_regtype.endswith(b'.right') : pclass = b'cl-right'
|
||||||
elif orig_regtype.endswith(b'.center') : pclass = 'cl-center'
|
elif orig_regtype.endswith(b'.center') : pclass = b'cl-center'
|
||||||
elif orig_regtype.endswith(b'.left') : pclass = 'cl-left'
|
elif orig_regtype.endswith(b'.left') : pclass = b'cl-left'
|
||||||
elif orig_regtype.endswith(b'.justify') : pclass = 'cl-justify'
|
elif orig_regtype.endswith(b'.justify') : pclass = b'cl-justify'
|
||||||
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
||||||
tag = 'p'
|
tag = 'p'
|
||||||
if pclass[3:6] == b'h1-' : tag = 'h4'
|
if pclass[3:6] == b'h1-' : tag = 'h4'
|
||||||
|
|||||||
@@ -353,11 +353,15 @@ class Decryptor(object):
|
|||||||
|
|
||||||
def decompress(self, bytes):
|
def decompress(self, bytes):
|
||||||
dc = zlib.decompressobj(-15)
|
dc = zlib.decompressobj(-15)
|
||||||
bytes = dc.decompress(bytes)
|
try:
|
||||||
ex = dc.decompress(b'Z') + dc.flush()
|
decompressed_bytes = dc.decompress(bytes)
|
||||||
if ex:
|
ex = dc.decompress(b'Z') + dc.flush()
|
||||||
bytes = bytes + ex
|
if ex:
|
||||||
return bytes
|
decompressed_bytes = decompressed_bytes + ex
|
||||||
|
except:
|
||||||
|
# possibly not compressed by zip - just return bytes
|
||||||
|
return bytes
|
||||||
|
return decompressed_bytes
|
||||||
|
|
||||||
def decrypt(self, path, data):
|
def decrypt(self, path, data):
|
||||||
if path.encode('utf-8') in self._encrypted:
|
if path.encode('utf-8') in self._encrypted:
|
||||||
@@ -411,12 +415,12 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
return 1
|
return 1
|
||||||
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
|
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
|
||||||
# Padded as per RSAES-PKCS1-v1_5
|
# Padded as per RSAES-PKCS1-v1_5
|
||||||
if len(bookkey) != 16:
|
if len(bookkey) > 16:
|
||||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
if bookkey[-17] == '\x00' or bookkey[-17] == 0:
|
||||||
|
bookkey = bookkey[-16:]
|
||||||
|
else:
|
||||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||||
return 2
|
return 2
|
||||||
else:
|
|
||||||
bookkey = bookkey[-16:]
|
|
||||||
encryption = inf.read('META-INF/encryption.xml')
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
decryptor = Decryptor(bookkey, encryption)
|
decryptor = Decryptor(bookkey, encryption)
|
||||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
|
|||||||
365
DeDRM_plugin/ineptpdf.py
Normal file → Executable file
365
DeDRM_plugin/ineptpdf.py
Normal file → Executable file
@@ -54,12 +54,14 @@ Decrypts Adobe ADEPT-encrypted PDF files.
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "9.0.0"
|
__version__ = "9.0.0"
|
||||||
|
|
||||||
|
import codecs
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import zlib
|
import zlib
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from io import BytesIO
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import itertools
|
import itertools
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
@@ -258,6 +260,7 @@ def _load_crypto_pycrypto():
|
|||||||
from Crypto.PublicKey import RSA as _RSA
|
from Crypto.PublicKey import RSA as _RSA
|
||||||
from Crypto.Cipher import ARC4 as _ARC4
|
from Crypto.Cipher import ARC4 as _ARC4
|
||||||
from Crypto.Cipher import AES as _AES
|
from Crypto.Cipher import AES as _AES
|
||||||
|
from Crypto.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
||||||
|
|
||||||
# ASN.1 parsing code from tlslite
|
# ASN.1 parsing code from tlslite
|
||||||
class ASN1Error(Exception):
|
class ASN1Error(Exception):
|
||||||
@@ -372,7 +375,7 @@ def _load_crypto_pycrypto():
|
|||||||
|
|
||||||
class RSA(object):
|
class RSA(object):
|
||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
key = ASN1Parser([ord(x) for x in der])
|
key = ASN1Parser([x for x in der])
|
||||||
key = [key.getChild(x).value for x in range(1, 4)]
|
key = [key.getChild(x).value for x in range(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
@@ -384,7 +387,7 @@ def _load_crypto_pycrypto():
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return self._rsa.decrypt(data)
|
return _PKCS1_v1_5.new(self._rsa).decrypt(data, 172)
|
||||||
|
|
||||||
return (ARC4, RSA, AES)
|
return (ARC4, RSA, AES)
|
||||||
|
|
||||||
@@ -403,7 +406,6 @@ def _load_crypto():
|
|||||||
ARC4, RSA, AES = _load_crypto()
|
ARC4, RSA, AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
|
|
||||||
# Do we generate cross reference streams on output?
|
# Do we generate cross reference streams on output?
|
||||||
@@ -440,7 +442,7 @@ def nunpack(s, default=0):
|
|||||||
elif l == 2:
|
elif l == 2:
|
||||||
return struct.unpack('>H', s)[0]
|
return struct.unpack('>H', s)[0]
|
||||||
elif l == 3:
|
elif l == 3:
|
||||||
return struct.unpack('>L', '\x00'+s)[0]
|
return struct.unpack('>L', b'\x00'+s)[0]
|
||||||
elif l == 4:
|
elif l == 4:
|
||||||
return struct.unpack('>L', s)[0]
|
return struct.unpack('>L', s)[0]
|
||||||
else:
|
else:
|
||||||
@@ -472,16 +474,16 @@ class PSLiteral(PSObject):
|
|||||||
Use PSLiteralTable.intern() instead.
|
Use PSLiteralTable.intern() instead.
|
||||||
'''
|
'''
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name.decode('utf-8')
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
name = []
|
name = []
|
||||||
for char in self.name:
|
for char in self.name:
|
||||||
if not char.isalnum():
|
if not char.isalnum():
|
||||||
char = b'#%02x' % ord(char)
|
char = '#%02x' % ord(char)
|
||||||
name.append(char)
|
name.append(char)
|
||||||
return b'/%s' % ''.join(name)
|
return '/%s' % ''.join(name)
|
||||||
|
|
||||||
# PSKeyword
|
# PSKeyword
|
||||||
class PSKeyword(PSObject):
|
class PSKeyword(PSObject):
|
||||||
@@ -491,7 +493,7 @@ class PSKeyword(PSObject):
|
|||||||
Use PSKeywordTable.intern() instead.
|
Use PSKeywordTable.intern() instead.
|
||||||
'''
|
'''
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name.decode('utf-8')
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -521,12 +523,12 @@ PSLiteralTable = PSSymbolTable(PSLiteral)
|
|||||||
PSKeywordTable = PSSymbolTable(PSKeyword)
|
PSKeywordTable = PSSymbolTable(PSKeyword)
|
||||||
LIT = PSLiteralTable.intern
|
LIT = PSLiteralTable.intern
|
||||||
KWD = PSKeywordTable.intern
|
KWD = PSKeywordTable.intern
|
||||||
KEYWORD_BRACE_BEGIN = KWD('{')
|
KEYWORD_BRACE_BEGIN = KWD(b'{')
|
||||||
KEYWORD_BRACE_END = KWD('}')
|
KEYWORD_BRACE_END = KWD(b'}')
|
||||||
KEYWORD_ARRAY_BEGIN = KWD('[')
|
KEYWORD_ARRAY_BEGIN = KWD(b'[')
|
||||||
KEYWORD_ARRAY_END = KWD(']')
|
KEYWORD_ARRAY_END = KWD(b']')
|
||||||
KEYWORD_DICT_BEGIN = KWD('<<')
|
KEYWORD_DICT_BEGIN = KWD(b'<<')
|
||||||
KEYWORD_DICT_END = KWD('>>')
|
KEYWORD_DICT_END = KWD(b'>>')
|
||||||
|
|
||||||
|
|
||||||
def literal_name(x):
|
def literal_name(x):
|
||||||
@@ -548,18 +550,18 @@ def keyword_name(x):
|
|||||||
|
|
||||||
## PSBaseParser
|
## PSBaseParser
|
||||||
##
|
##
|
||||||
EOL = re.compile(r'[\r\n]')
|
EOL = re.compile(rb'[\r\n]')
|
||||||
SPC = re.compile(r'\s')
|
SPC = re.compile(rb'\s')
|
||||||
NONSPC = re.compile(r'\S')
|
NONSPC = re.compile(rb'\S')
|
||||||
HEX = re.compile(r'[0-9a-fA-F]')
|
HEX = re.compile(rb'[0-9a-fA-F]')
|
||||||
END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
|
END_LITERAL = re.compile(rb'[#/%\[\]()<>{}\s]')
|
||||||
END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
|
END_HEX_STRING = re.compile(rb'[^\s0-9a-fA-F]')
|
||||||
HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
|
HEX_PAIR = re.compile(rb'[0-9a-fA-F]{2}|.')
|
||||||
END_NUMBER = re.compile(r'[^0-9]')
|
END_NUMBER = re.compile(rb'[^0-9]')
|
||||||
END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
|
END_KEYWORD = re.compile(rb'[#/%\[\]()<>{}\s]')
|
||||||
END_STRING = re.compile(r'[()\134]')
|
END_STRING = re.compile(rb'[()\\]')
|
||||||
OCT_STRING = re.compile(r'[0-7]')
|
OCT_STRING = re.compile(rb'[0-7]')
|
||||||
ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 }
|
ESC_STRING = { b'b':8, b't':9, b'n':10, b'f':12, b'r':13, b'(':40, b')':41, b'\\':92 }
|
||||||
|
|
||||||
class PSBaseParser(object):
|
class PSBaseParser(object):
|
||||||
|
|
||||||
@@ -591,7 +593,6 @@ class PSBaseParser(object):
|
|||||||
if not pos:
|
if not pos:
|
||||||
pos = self.bufpos+self.charpos
|
pos = self.bufpos+self.charpos
|
||||||
self.fp.seek(pos)
|
self.fp.seek(pos)
|
||||||
##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
|
|
||||||
self.fp.seek(pos0)
|
self.fp.seek(pos0)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -602,7 +603,7 @@ class PSBaseParser(object):
|
|||||||
self.fp.seek(pos)
|
self.fp.seek(pos)
|
||||||
# reset the status for nextline()
|
# reset the status for nextline()
|
||||||
self.bufpos = pos
|
self.bufpos = pos
|
||||||
self.buf = ''
|
self.buf = b''
|
||||||
self.charpos = 0
|
self.charpos = 0
|
||||||
# reset the status for nexttoken()
|
# reset the status for nexttoken()
|
||||||
self.parse1 = self.parse_main
|
self.parse1 = self.parse_main
|
||||||
@@ -624,32 +625,32 @@ class PSBaseParser(object):
|
|||||||
if not m:
|
if not m:
|
||||||
return (self.parse_main, len(s))
|
return (self.parse_main, len(s))
|
||||||
j = m.start(0)
|
j = m.start(0)
|
||||||
c = s[j]
|
c = bytes([s[j]])
|
||||||
self.tokenstart = self.bufpos+j
|
self.tokenstart = self.bufpos+j
|
||||||
if c == '%':
|
if c == b'%':
|
||||||
self.token = '%'
|
self.token = c
|
||||||
return (self.parse_comment, j+1)
|
return (self.parse_comment, j+1)
|
||||||
if c == '/':
|
if c == b'/':
|
||||||
self.token = ''
|
self.token = b''
|
||||||
return (self.parse_literal, j+1)
|
return (self.parse_literal, j+1)
|
||||||
if c in '-+' or c.isdigit():
|
if c in b'-+' or c.isdigit():
|
||||||
self.token = c
|
self.token = c
|
||||||
return (self.parse_number, j+1)
|
return (self.parse_number, j+1)
|
||||||
if c == '.':
|
if c == b'.':
|
||||||
self.token = c
|
self.token = c
|
||||||
return (self.parse_decimal, j+1)
|
return (self.parse_decimal, j+1)
|
||||||
if c.isalpha():
|
if c.isalpha():
|
||||||
self.token = c
|
self.token = c
|
||||||
return (self.parse_keyword, j+1)
|
return (self.parse_keyword, j+1)
|
||||||
if c == '(':
|
if c == b'(':
|
||||||
self.token = ''
|
self.token = b''
|
||||||
self.paren = 1
|
self.paren = 1
|
||||||
return (self.parse_string, j+1)
|
return (self.parse_string, j+1)
|
||||||
if c == '<':
|
if c == b'<':
|
||||||
self.token = ''
|
self.token = b''
|
||||||
return (self.parse_wopen, j+1)
|
return (self.parse_wopen, j+1)
|
||||||
if c == '>':
|
if c == b'>':
|
||||||
self.token = ''
|
self.token = b''
|
||||||
return (self.parse_wclose, j+1)
|
return (self.parse_wclose, j+1)
|
||||||
self.add_token(KWD(c))
|
self.add_token(KWD(c))
|
||||||
return (self.parse_main, j+1)
|
return (self.parse_main, j+1)
|
||||||
@@ -676,20 +677,20 @@ class PSBaseParser(object):
|
|||||||
return (self.parse_literal, len(s))
|
return (self.parse_literal, len(s))
|
||||||
j = m.start(0)
|
j = m.start(0)
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
c = s[j]
|
c = bytes([s[j]])
|
||||||
if c == '#':
|
if c == b'#':
|
||||||
self.hex = ''
|
self.hex = b''
|
||||||
return (self.parse_literal_hex, j+1)
|
return (self.parse_literal_hex, j+1)
|
||||||
self.add_token(LIT(self.token))
|
self.add_token(LIT(self.token))
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_literal_hex(self, s, i):
|
def parse_literal_hex(self, s, i):
|
||||||
c = s[i]
|
c = bytes([s[i]])
|
||||||
if HEX.match(c) and len(self.hex) < 2:
|
if HEX.match(c) and len(self.hex) < 2:
|
||||||
self.hex += c
|
self.hex += c
|
||||||
return (self.parse_literal_hex, i+1)
|
return (self.parse_literal_hex, i+1)
|
||||||
if self.hex:
|
if self.hex:
|
||||||
self.token += chr(int(self.hex, 16))
|
self.token += bytes([int(self.hex, 16)])
|
||||||
return (self.parse_literal, i)
|
return (self.parse_literal, i)
|
||||||
|
|
||||||
def parse_number(self, s, i):
|
def parse_number(self, s, i):
|
||||||
@@ -699,8 +700,8 @@ class PSBaseParser(object):
|
|||||||
return (self.parse_number, len(s))
|
return (self.parse_number, len(s))
|
||||||
j = m.start(0)
|
j = m.start(0)
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
c = s[j]
|
c = bytes([s[j]])
|
||||||
if c == '.':
|
if c == b'.':
|
||||||
self.token += c
|
self.token += c
|
||||||
return (self.parse_decimal, j+1)
|
return (self.parse_decimal, j+1)
|
||||||
try:
|
try:
|
||||||
@@ -716,7 +717,7 @@ class PSBaseParser(object):
|
|||||||
return (self.parse_decimal, len(s))
|
return (self.parse_decimal, len(s))
|
||||||
j = m.start(0)
|
j = m.start(0)
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
self.add_token(Decimal(self.token))
|
self.add_token(Decimal(self.token.decode('utf-8')))
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_keyword(self, s, i):
|
def parse_keyword(self, s, i):
|
||||||
@@ -742,58 +743,59 @@ class PSBaseParser(object):
|
|||||||
return (self.parse_string, len(s))
|
return (self.parse_string, len(s))
|
||||||
j = m.start(0)
|
j = m.start(0)
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
c = s[j]
|
c = bytes([s[j]])
|
||||||
if c == '\\':
|
if c == b'\\':
|
||||||
self.oct = ''
|
self.oct = ''
|
||||||
return (self.parse_string_1, j+1)
|
return (self.parse_string_1, j+1)
|
||||||
if c == '(':
|
if c == b'(':
|
||||||
self.paren += 1
|
self.paren += 1
|
||||||
self.token += c
|
self.token += c
|
||||||
return (self.parse_string, j+1)
|
return (self.parse_string, j+1)
|
||||||
if c == ')':
|
if c == b')':
|
||||||
self.paren -= 1
|
self.paren -= 1
|
||||||
if self.paren:
|
if self.paren:
|
||||||
self.token += c
|
self.token += c
|
||||||
return (self.parse_string, j+1)
|
return (self.parse_string, j+1)
|
||||||
self.add_token(self.token)
|
self.add_token(self.token)
|
||||||
return (self.parse_main, j+1)
|
return (self.parse_main, j+1)
|
||||||
|
|
||||||
def parse_string_1(self, s, i):
|
def parse_string_1(self, s, i):
|
||||||
c = s[i]
|
c = bytes([s[i]])
|
||||||
if OCT_STRING.match(c) and len(self.oct) < 3:
|
if OCT_STRING.match(c) and len(self.oct) < 3:
|
||||||
self.oct += c
|
self.oct += c
|
||||||
return (self.parse_string_1, i+1)
|
return (self.parse_string_1, i+1)
|
||||||
if self.oct:
|
if self.oct:
|
||||||
self.token += chr(int(self.oct, 8))
|
self.token += bytes([int(self.oct, 8)])
|
||||||
return (self.parse_string, i)
|
return (self.parse_string, i)
|
||||||
if c in ESC_STRING:
|
if c in ESC_STRING:
|
||||||
self.token += chr(ESC_STRING[c])
|
self.token += bytes([ESC_STRING[c]])
|
||||||
return (self.parse_string, i+1)
|
return (self.parse_string, i+1)
|
||||||
|
|
||||||
def parse_wopen(self, s, i):
|
def parse_wopen(self, s, i):
|
||||||
c = s[i]
|
c = bytes([s[i]])
|
||||||
if c.isspace() or HEX.match(c):
|
if c.isspace() or HEX.match(c):
|
||||||
return (self.parse_hexstring, i)
|
return (self.parse_hexstring, i)
|
||||||
if c == '<':
|
if c == b'<':
|
||||||
self.add_token(KEYWORD_DICT_BEGIN)
|
self.add_token(KEYWORD_DICT_BEGIN)
|
||||||
i += 1
|
i += 1
|
||||||
return (self.parse_main, i)
|
return (self.parse_main, i)
|
||||||
|
|
||||||
def parse_wclose(self, s, i):
|
def parse_wclose(self, s, i):
|
||||||
c = s[i]
|
c = bytes([s[i]])
|
||||||
if c == '>':
|
if c == b'>':
|
||||||
self.add_token(KEYWORD_DICT_END)
|
self.add_token(KEYWORD_DICT_END)
|
||||||
i += 1
|
i += 1
|
||||||
return (self.parse_main, i)
|
return (self.parse_main, i)
|
||||||
|
|
||||||
def parse_hexstring(self, s, i):
|
def parse_hexstring(self, s, i):
|
||||||
m = END_HEX_STRING.search(s, i)
|
m1 = END_HEX_STRING.search(s, i)
|
||||||
if not m:
|
if not m1:
|
||||||
self.token += s[i:]
|
self.token += s[i:]
|
||||||
return (self.parse_hexstring, len(s))
|
return (self.parse_hexstring, len(s))
|
||||||
j = m.start(0)
|
j = m1.start(0)
|
||||||
self.token += s[i:j]
|
self.token += s[i:j]
|
||||||
token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
|
token = HEX_PAIR.sub(lambda m2: bytes([int(m2.group(0), 16)]),
|
||||||
SPC.sub('', self.token))
|
SPC.sub(b'', self.token))
|
||||||
self.add_token(token)
|
self.add_token(token)
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
@@ -808,15 +810,15 @@ class PSBaseParser(object):
|
|||||||
'''
|
'''
|
||||||
Fetches a next line that ends either with \\r or \\n.
|
Fetches a next line that ends either with \\r or \\n.
|
||||||
'''
|
'''
|
||||||
linebuf = ''
|
linebuf = b''
|
||||||
linepos = self.bufpos + self.charpos
|
linepos = self.bufpos + self.charpos
|
||||||
eol = False
|
eol = False
|
||||||
while 1:
|
while 1:
|
||||||
self.fillbuf()
|
self.fillbuf()
|
||||||
if eol:
|
if eol:
|
||||||
c = self.buf[self.charpos]
|
c = bytes([self.buf[self.charpos]])
|
||||||
# handle '\r\n'
|
# handle '\r\n'
|
||||||
if c == '\n':
|
if c == b'\n':
|
||||||
linebuf += c
|
linebuf += c
|
||||||
self.charpos += 1
|
self.charpos += 1
|
||||||
break
|
break
|
||||||
@@ -824,7 +826,7 @@ class PSBaseParser(object):
|
|||||||
if m:
|
if m:
|
||||||
linebuf += self.buf[self.charpos:m.end(0)]
|
linebuf += self.buf[self.charpos:m.end(0)]
|
||||||
self.charpos = m.end(0)
|
self.charpos = m.end(0)
|
||||||
if linebuf[-1] == '\r':
|
if bytes([linebuf[-1]]) == b'\r':
|
||||||
eol = True
|
eol = True
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
@@ -840,7 +842,7 @@ class PSBaseParser(object):
|
|||||||
'''
|
'''
|
||||||
self.fp.seek(0, 2)
|
self.fp.seek(0, 2)
|
||||||
pos = self.fp.tell()
|
pos = self.fp.tell()
|
||||||
buf = ''
|
buf = b''
|
||||||
while 0 < pos:
|
while 0 < pos:
|
||||||
prevpos = pos
|
prevpos = pos
|
||||||
pos = max(0, pos-self.BUFSIZ)
|
pos = max(0, pos-self.BUFSIZ)
|
||||||
@@ -848,13 +850,13 @@ class PSBaseParser(object):
|
|||||||
s = self.fp.read(prevpos-pos)
|
s = self.fp.read(prevpos-pos)
|
||||||
if not s: break
|
if not s: break
|
||||||
while 1:
|
while 1:
|
||||||
n = max(s.rfind('\r'), s.rfind('\n'))
|
n = max(s.rfind(b'\r'), s.rfind(b'\n'))
|
||||||
if n == -1:
|
if n == -1:
|
||||||
buf = s + buf
|
buf = s + buf
|
||||||
break
|
break
|
||||||
yield s[n:]+buf
|
yield s[n:]+buf
|
||||||
s = s[:n]
|
s = s[:n]
|
||||||
buf = ''
|
buf = b''
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -910,17 +912,17 @@ class PSStackParser(PSBaseParser):
|
|||||||
|
|
||||||
def nextobject(self, direct=False):
|
def nextobject(self, direct=False):
|
||||||
'''
|
'''
|
||||||
Yields a list of objects: keywords, literals, strings,
|
Yields a list of objects: keywords, literals, strings (byte arrays),
|
||||||
numbers, arrays and dictionaries. Arrays and dictionaries
|
numbers, arrays and dictionaries. Arrays and dictionaries
|
||||||
are represented as Python sequence and dictionaries.
|
are represented as Python sequence and dictionaries.
|
||||||
'''
|
'''
|
||||||
while not self.results:
|
while not self.results:
|
||||||
(pos, token) = self.nexttoken()
|
(pos, token) = self.nexttoken()
|
||||||
##print (pos,token), (self.curtype, self.curstack)
|
|
||||||
if (isinstance(token, int) or
|
if (isinstance(token, int) or
|
||||||
isinstance(token, Decimal) or
|
isinstance(token, Decimal) or
|
||||||
isinstance(token, bool) or
|
isinstance(token, bool) or
|
||||||
isinstance(token, str) or
|
isinstance(token, bytearray) or
|
||||||
|
isinstance(token, bytes) or
|
||||||
isinstance(token, PSLiteral)):
|
isinstance(token, PSLiteral)):
|
||||||
# normal token
|
# normal token
|
||||||
self.push((pos, token))
|
self.push((pos, token))
|
||||||
@@ -963,10 +965,10 @@ class PSStackParser(PSBaseParser):
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
LITERAL_CRYPT = PSLiteralTable.intern('Crypt')
|
LITERAL_CRYPT = LIT(b'Crypt')
|
||||||
LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl'))
|
LITERALS_FLATE_DECODE = (LIT(b'FlateDecode'), LIT(b'Fl'))
|
||||||
LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW'))
|
LITERALS_LZW_DECODE = (LIT(b'LZWDecode'), LIT(b'LZW'))
|
||||||
LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85'))
|
LITERALS_ASCII85_DECODE = (LIT(b'ASCII85Decode'), LIT(b'A85'))
|
||||||
|
|
||||||
|
|
||||||
## PDF Objects
|
## PDF Objects
|
||||||
@@ -1020,7 +1022,7 @@ def resolve_all(x):
|
|||||||
if isinstance(x, list):
|
if isinstance(x, list):
|
||||||
x = [ resolve_all(v) for v in x ]
|
x = [ resolve_all(v) for v in x ]
|
||||||
elif isinstance(x, dict):
|
elif isinstance(x, dict):
|
||||||
for (k,v) in x.iteritems():
|
for (k,v) in iter(x.items()):
|
||||||
x[k] = resolve_all(v)
|
x[k] = resolve_all(v)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
@@ -1028,13 +1030,13 @@ def decipher_all(decipher, objid, genno, x):
|
|||||||
'''
|
'''
|
||||||
Recursively decipher X.
|
Recursively decipher X.
|
||||||
'''
|
'''
|
||||||
if isinstance(x, str):
|
if isinstance(x, bytearray) or isinstance(x,bytes):
|
||||||
return decipher(objid, genno, x)
|
return decipher(objid, genno, x)
|
||||||
decf = lambda v: decipher_all(decipher, objid, genno, v)
|
decf = lambda v: decipher_all(decipher, objid, genno, v)
|
||||||
if isinstance(x, list):
|
if isinstance(x, list):
|
||||||
x = [decf(v) for v in x]
|
x = [decf(v) for v in x]
|
||||||
elif isinstance(x, dict):
|
elif isinstance(x, dict):
|
||||||
x = dict((k, decf(v)) for (k, v) in x.iteritems())
|
x = dict((k, decf(v)) for (k, v) in iter(x.items()))
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
@@ -1065,7 +1067,7 @@ def num_value(x):
|
|||||||
|
|
||||||
def str_value(x):
|
def str_value(x):
|
||||||
x = resolve1(x)
|
x = resolve1(x)
|
||||||
if not isinstance(x, str):
|
if not (isinstance(x, bytearray) or isinstance(x, bytes)):
|
||||||
if STRICT:
|
if STRICT:
|
||||||
raise PDFTypeError('String required: %r' % x)
|
raise PDFTypeError('String required: %r' % x)
|
||||||
return ''
|
return ''
|
||||||
@@ -1166,7 +1168,6 @@ class PDFStream(PDFObject):
|
|||||||
if 'Filter' not in self.dic:
|
if 'Filter' not in self.dic:
|
||||||
self.data = data
|
self.data = data
|
||||||
self.rawdata = None
|
self.rawdata = None
|
||||||
##print self.dict
|
|
||||||
return
|
return
|
||||||
filters = self.dic['Filter']
|
filters = self.dic['Filter']
|
||||||
if not isinstance(filters, list):
|
if not isinstance(filters, list):
|
||||||
@@ -1176,7 +1177,7 @@ class PDFStream(PDFObject):
|
|||||||
# will get errors if the document is encrypted.
|
# will get errors if the document is encrypted.
|
||||||
data = zlib.decompress(data)
|
data = zlib.decompress(data)
|
||||||
elif f in LITERALS_LZW_DECODE:
|
elif f in LITERALS_LZW_DECODE:
|
||||||
data = ''.join(LZWDecoder(BytesIO(data)).run())
|
data = b''.join(LZWDecoder(BytesIO(data)).run())
|
||||||
elif f in LITERALS_ASCII85_DECODE:
|
elif f in LITERALS_ASCII85_DECODE:
|
||||||
data = ascii85decode(data)
|
data = ascii85decode(data)
|
||||||
elif f == LITERAL_CRYPT:
|
elif f == LITERAL_CRYPT:
|
||||||
@@ -1204,7 +1205,7 @@ class PDFStream(PDFObject):
|
|||||||
pred = data[i]
|
pred = data[i]
|
||||||
ent1 = data[i+1:i+1+columns]
|
ent1 = data[i+1:i+1+columns]
|
||||||
if pred == b'\x02':
|
if pred == b'\x02':
|
||||||
ent1 = ''.join(bytes([(a+b) & 255]) \
|
ent1 = b''.join(bytes([(a+b) & 255]) \
|
||||||
for (a,b) in zip(ent0,ent1))
|
for (a,b) in zip(ent0,ent1))
|
||||||
buf += ent1
|
buf += ent1
|
||||||
ent0 = ent1
|
ent0 = ent1
|
||||||
@@ -1239,11 +1240,11 @@ class PDFEncryptionError(PDFException): pass
|
|||||||
class PDFPasswordIncorrect(PDFEncryptionError): pass
|
class PDFPasswordIncorrect(PDFEncryptionError): pass
|
||||||
|
|
||||||
# some predefined literals and keywords.
|
# some predefined literals and keywords.
|
||||||
LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm')
|
LITERAL_OBJSTM = LIT(b'ObjStm')
|
||||||
LITERAL_XREF = PSLiteralTable.intern('XRef')
|
LITERAL_XREF = LIT(b'XRef')
|
||||||
LITERAL_PAGE = PSLiteralTable.intern('Page')
|
LITERAL_PAGE = LIT(b'Page')
|
||||||
LITERAL_PAGES = PSLiteralTable.intern('Pages')
|
LITERAL_PAGES = LIT(b'Pages')
|
||||||
LITERAL_CATALOG = PSLiteralTable.intern('Catalog')
|
LITERAL_CATALOG = LIT(b'Catalog')
|
||||||
|
|
||||||
|
|
||||||
## XRefs
|
## XRefs
|
||||||
@@ -1261,7 +1262,7 @@ class PDFXRef(object):
|
|||||||
return '<PDFXRef: objs=%d>' % len(self.offsets)
|
return '<PDFXRef: objs=%d>' % len(self.offsets)
|
||||||
|
|
||||||
def objids(self):
|
def objids(self):
|
||||||
return self.offsets.iterkeys()
|
return iter(self.offsets.keys())
|
||||||
|
|
||||||
def load(self, parser):
|
def load(self, parser):
|
||||||
self.offsets = {}
|
self.offsets = {}
|
||||||
@@ -1272,10 +1273,10 @@ class PDFXRef(object):
|
|||||||
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
|
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
|
||||||
if not line:
|
if not line:
|
||||||
raise PDFNoValidXRef('Premature eof: %r' % parser)
|
raise PDFNoValidXRef('Premature eof: %r' % parser)
|
||||||
if line.startswith('trailer'):
|
if line.startswith(b'trailer'):
|
||||||
parser.seek(pos)
|
parser.seek(pos)
|
||||||
break
|
break
|
||||||
f = line.strip().split(' ')
|
f = line.strip().split(b' ')
|
||||||
if len(f) != 2:
|
if len(f) != 2:
|
||||||
raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
|
raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
|
||||||
try:
|
try:
|
||||||
@@ -1287,16 +1288,17 @@ class PDFXRef(object):
|
|||||||
(_, line) = parser.nextline()
|
(_, line) = parser.nextline()
|
||||||
except PSEOF:
|
except PSEOF:
|
||||||
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
|
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
|
||||||
f = line.strip().split(' ')
|
f = line.strip().split(b' ')
|
||||||
if len(f) != 3:
|
if len(f) != 3:
|
||||||
raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
|
raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
|
||||||
(pos, genno, use) = f
|
(pos, genno, use) = f
|
||||||
if use != 'n': continue
|
if use != b'n':
|
||||||
self.offsets[objid] = (int(genno), int(pos))
|
continue
|
||||||
|
self.offsets[objid] = (int(genno.decode('utf-8')), int(pos.decode('utf-8')))
|
||||||
self.load_trailer(parser)
|
self.load_trailer(parser)
|
||||||
return
|
return
|
||||||
|
|
||||||
KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
|
KEYWORD_TRAILER = KWD(b'trailer')
|
||||||
def load_trailer(self, parser):
|
def load_trailer(self, parser):
|
||||||
try:
|
try:
|
||||||
(_,kwd) = parser.nexttoken()
|
(_,kwd) = parser.nexttoken()
|
||||||
@@ -1401,7 +1403,8 @@ class PDFDocument(object):
|
|||||||
# set_parser(parser)
|
# set_parser(parser)
|
||||||
# Associates the document with an (already initialized) parser object.
|
# Associates the document with an (already initialized) parser object.
|
||||||
def set_parser(self, parser):
|
def set_parser(self, parser):
|
||||||
if self.parser: return
|
if self.parser:
|
||||||
|
return
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
# The document is set to be temporarily ready during collecting
|
# The document is set to be temporarily ready during collecting
|
||||||
# all the basic information about the document, e.g.
|
# all the basic information about the document, e.g.
|
||||||
@@ -1423,13 +1426,13 @@ class PDFDocument(object):
|
|||||||
dict_value(trailer['Encrypt']))
|
dict_value(trailer['Encrypt']))
|
||||||
# fix for bad files
|
# fix for bad files
|
||||||
except:
|
except:
|
||||||
self.encryption = ('ffffffffffffffffffffffffffffffffffff',
|
self.encryption = (b'ffffffffffffffffffffffffffffffffffff',
|
||||||
dict_value(trailer['Encrypt']))
|
dict_value(trailer['Encrypt']))
|
||||||
if 'Root' in trailer:
|
if 'Root' in trailer:
|
||||||
self.set_root(dict_value(trailer['Root']))
|
self.set_root(dict_value(trailer['Root']))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
|
raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
|
||||||
# The document is set to be non-ready again, until all the
|
# The document is set to be non-ready again, until all the
|
||||||
# proper initialization (asking the password key and
|
# proper initialization (asking the password key and
|
||||||
# verifying the access permission, so on) is finished.
|
# verifying the access permission, so on) is finished.
|
||||||
@@ -1450,7 +1453,7 @@ class PDFDocument(object):
|
|||||||
# Perform the initialization with a given password.
|
# Perform the initialization with a given password.
|
||||||
# This step is mandatory even if there's no password associated
|
# This step is mandatory even if there's no password associated
|
||||||
# with the document.
|
# with the document.
|
||||||
def initialize(self, password=''):
|
def initialize(self, password=b''):
|
||||||
if not self.encryption:
|
if not self.encryption:
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
self.ready = True
|
self.ready = True
|
||||||
@@ -1477,14 +1480,14 @@ class PDFDocument(object):
|
|||||||
def genkey_adobe_ps(self, param):
|
def genkey_adobe_ps(self, param):
|
||||||
# nice little offline principal keys dictionary
|
# nice little offline principal keys dictionary
|
||||||
# global static principal key for German Onleihe / Bibliothek Digital
|
# global static principal key for German Onleihe / Bibliothek Digital
|
||||||
principalkeys = { 'bibliothek-digital.de': 'rRwGv2tbpKov1krvv7PO0ws9S436/lArPlfipz5Pqhw='.decode('base64')}
|
principalkeys = { b'bibliothek-digital.de': codecs.decode(b'rRwGv2tbpKov1krvv7PO0ws9S436/lArPlfipz5Pqhw=','base64')}
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
length = int_value(param.get('Length', 0)) / 8
|
length = int_value(param.get('Length', 0)) // 8
|
||||||
edcdata = str_value(param.get('EDCData')).decode('base64')
|
edcdata = str_value(param.get('EDCData')).decode('base64')
|
||||||
pdrllic = str_value(param.get('PDRLLic')).decode('base64')
|
pdrllic = str_value(param.get('PDRLLic')).decode('base64')
|
||||||
pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
|
pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
|
||||||
edclist = []
|
edclist = []
|
||||||
for pair in edcdata.split('\n'):
|
for pair in edcdata.split(b'\n'):
|
||||||
edclist.append(pair)
|
edclist.append(pair)
|
||||||
# principal key request
|
# principal key request
|
||||||
for key in principalkeys:
|
for key in principalkeys:
|
||||||
@@ -1493,9 +1496,9 @@ class PDFDocument(object):
|
|||||||
else:
|
else:
|
||||||
raise ADEPTError('Cannot find principal key for this pdf')
|
raise ADEPTError('Cannot find principal key for this pdf')
|
||||||
shakey = SHA256(principalkey)
|
shakey = SHA256(principalkey)
|
||||||
ivector = 16 * chr(0)
|
ivector = bytes(16) # 16 zero bytes
|
||||||
plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
|
plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
|
||||||
if plaintext[-16:] != 16 * chr(16):
|
if plaintext[-16:] != bytearray(b'\0x10')*16:
|
||||||
raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
|
raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
|
||||||
pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
|
pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
|
||||||
if pdrlpol[-1] < 1 or pdrlpol[-1] > 16:
|
if pdrlpol[-1] < 1 or pdrlpol[-1] > 16:
|
||||||
@@ -1505,8 +1508,8 @@ class PDFDocument(object):
|
|||||||
pdrlpol = pdrlpol[:cutter]
|
pdrlpol = pdrlpol[:cutter]
|
||||||
return plaintext[:16]
|
return plaintext[:16]
|
||||||
|
|
||||||
PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
|
PASSWORD_PADDING = b'(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
|
||||||
'\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
|
b'\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
|
||||||
# experimental aes pw support
|
# experimental aes pw support
|
||||||
def initialize_standard(self, password, docid, param):
|
def initialize_standard(self, password, docid, param):
|
||||||
# copy from a global variable
|
# copy from a global variable
|
||||||
@@ -1523,7 +1526,7 @@ class PDFDocument(object):
|
|||||||
try:
|
try:
|
||||||
EncMetadata = str_value(param['EncryptMetadata'])
|
EncMetadata = str_value(param['EncryptMetadata'])
|
||||||
except:
|
except:
|
||||||
EncMetadata = 'True'
|
EncMetadata = b'True'
|
||||||
self.is_printable = bool(P & 4)
|
self.is_printable = bool(P & 4)
|
||||||
self.is_modifiable = bool(P & 8)
|
self.is_modifiable = bool(P & 8)
|
||||||
self.is_extractable = bool(P & 16)
|
self.is_extractable = bool(P & 16)
|
||||||
@@ -1540,12 +1543,12 @@ class PDFDocument(object):
|
|||||||
hash.update(docid[0]) # 5
|
hash.update(docid[0]) # 5
|
||||||
# aes special handling if metadata isn't encrypted
|
# aes special handling if metadata isn't encrypted
|
||||||
if EncMetadata == ('False' or 'false'):
|
if EncMetadata == ('False' or 'false'):
|
||||||
hash.update('ffffffff'.decode('hex'))
|
hash.update(codecs.decode(b'ffffffff','hex'))
|
||||||
if 5 <= R:
|
if 5 <= R:
|
||||||
# 8
|
# 8
|
||||||
for _ in range(50):
|
for _ in range(50):
|
||||||
hash = hashlib.md5(hash.digest()[:length/8])
|
hash = hashlib.md5(hash.digest()[:length//8])
|
||||||
key = hash.digest()[:length/8]
|
key = hash.digest()[:length//8]
|
||||||
if R == 2:
|
if R == 2:
|
||||||
# Algorithm 3.4
|
# Algorithm 3.4
|
||||||
u1 = ARC4.new(key).decrypt(password)
|
u1 = ARC4.new(key).decrypt(password)
|
||||||
@@ -1555,7 +1558,7 @@ class PDFDocument(object):
|
|||||||
hash.update(docid[0]) # 3
|
hash.update(docid[0]) # 3
|
||||||
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
||||||
for i in range(1,19+1):
|
for i in range(1,19+1):
|
||||||
k = ''.join(bytes([c ^ i]) for c in key )
|
k = b''.join(bytes([c ^ i]) for c in key )
|
||||||
x = ARC4.new(k).decrypt(x)
|
x = ARC4.new(k).decrypt(x)
|
||||||
u1 = x+x # 32bytes total
|
u1 = x+x # 32bytes total
|
||||||
if R == 2:
|
if R == 2:
|
||||||
@@ -1587,17 +1590,19 @@ class PDFDocument(object):
|
|||||||
def initialize_ebx(self, password, docid, param):
|
def initialize_ebx(self, password, docid, param):
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
rsa = RSA(password)
|
rsa = RSA(password)
|
||||||
length = int_value(param.get('Length', 0)) / 8
|
length = int_value(param.get('Length', 0)) // 8
|
||||||
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
|
rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
|
||||||
rights = zlib.decompress(rights, -15)
|
rights = zlib.decompress(rights, -15)
|
||||||
rights = etree.fromstring(rights)
|
rights = etree.fromstring(rights)
|
||||||
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
||||||
bookkey = ''.join(rights.findtext(expr)).decode('base64')
|
bookkey = codecs.decode(''.join(rights.findtext(expr)).encode('utf-8'),'base64')
|
||||||
bookkey = rsa.decrypt(bookkey)
|
bookkey = rsa.decrypt(bookkey)
|
||||||
if bookkey[0] != '\x02':
|
#if bookkey[0] != 2:
|
||||||
raise ADEPTError('error decrypting book session key')
|
# raise ADEPTError('error decrypting book session key')
|
||||||
index = bookkey.index('\0') + 1
|
if len(bookkey) > 16:
|
||||||
bookkey = bookkey[index:]
|
if bookkey[-17] == '\x00' or bookkey[-17] == 0:
|
||||||
|
bookkey = bookkey[-16:]
|
||||||
|
length = 16
|
||||||
ebx_V = int_value(param.get('V', 4))
|
ebx_V = int_value(param.get('V', 4))
|
||||||
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
||||||
# added because of improper booktype / decryption book session key errors
|
# added because of improper booktype / decryption book session key errors
|
||||||
@@ -1643,7 +1648,7 @@ class PDFDocument(object):
|
|||||||
objid = struct.pack('<L', objid ^ 0x3569ac)
|
objid = struct.pack('<L', objid ^ 0x3569ac)
|
||||||
genno = struct.pack('<L', genno ^ 0xca96)
|
genno = struct.pack('<L', genno ^ 0xca96)
|
||||||
key = self.decrypt_key
|
key = self.decrypt_key
|
||||||
key += objid[0] + genno[0] + objid[1] + genno[1] + objid[2] + 'sAlT'
|
key += objid[0] + genno[0] + objid[1] + genno[1] + objid[2] + b'sAlT'
|
||||||
hash = hashlib.md5(key)
|
hash = hashlib.md5(key)
|
||||||
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
||||||
return key
|
return key
|
||||||
@@ -1652,7 +1657,7 @@ class PDFDocument(object):
|
|||||||
def genkey_v4(self, objid, genno):
|
def genkey_v4(self, objid, genno):
|
||||||
objid = struct.pack('<L', objid)[:3]
|
objid = struct.pack('<L', objid)[:3]
|
||||||
genno = struct.pack('<L', genno)[:2]
|
genno = struct.pack('<L', genno)[:2]
|
||||||
key = self.decrypt_key + objid + genno + 'sAlT'
|
key = self.decrypt_key + objid + genno + b'sAlT'
|
||||||
hash = hashlib.md5(key)
|
hash = hashlib.md5(key)
|
||||||
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
|
||||||
return key
|
return key
|
||||||
@@ -1664,7 +1669,6 @@ class PDFDocument(object):
|
|||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * plaintext[-1]
|
cutter = -1 * plaintext[-1]
|
||||||
#print cutter
|
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
@@ -1675,7 +1679,6 @@ class PDFDocument(object):
|
|||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * plaintext[-1]
|
cutter = -1 * plaintext[-1]
|
||||||
#print cutter
|
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
@@ -1684,7 +1687,7 @@ class PDFDocument(object):
|
|||||||
return ARC4.new(key).decrypt(data)
|
return ARC4.new(key).decrypt(data)
|
||||||
|
|
||||||
|
|
||||||
KEYWORD_OBJ = PSKeywordTable.intern('obj')
|
KEYWORD_OBJ = KWD(b'obj')
|
||||||
|
|
||||||
def getobj(self, objid):
|
def getobj(self, objid):
|
||||||
if not self.ready:
|
if not self.ready:
|
||||||
@@ -1791,11 +1794,11 @@ class PDFParser(PSStackParser):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<PDFParser>'
|
return '<PDFParser>'
|
||||||
|
|
||||||
KEYWORD_R = PSKeywordTable.intern('R')
|
KEYWORD_R = KWD(b'R')
|
||||||
KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj')
|
KEYWORD_ENDOBJ = KWD(b'endobj')
|
||||||
KEYWORD_STREAM = PSKeywordTable.intern('stream')
|
KEYWORD_STREAM = KWD(b'stream')
|
||||||
KEYWORD_XREF = PSKeywordTable.intern('xref')
|
KEYWORD_XREF = KWD(b'xref')
|
||||||
KEYWORD_STARTXREF = PSKeywordTable.intern('startxref')
|
KEYWORD_STARTXREF = KWD(b'startxref')
|
||||||
def do_keyword(self, pos, token):
|
def do_keyword(self, pos, token):
|
||||||
if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
|
if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
|
||||||
self.add_results(*self.pop(1))
|
self.add_results(*self.pop(1))
|
||||||
@@ -1843,8 +1846,8 @@ class PDFParser(PSStackParser):
|
|||||||
if STRICT:
|
if STRICT:
|
||||||
raise PDFSyntaxError('Unexpected EOF')
|
raise PDFSyntaxError('Unexpected EOF')
|
||||||
break
|
break
|
||||||
if 'endstream' in line:
|
if b'endstream' in line:
|
||||||
i = line.index('endstream')
|
i = line.index(b'endstream')
|
||||||
objlen += i
|
objlen += i
|
||||||
data += line[:i]
|
data += line[:i]
|
||||||
break
|
break
|
||||||
@@ -1864,7 +1867,7 @@ class PDFParser(PSStackParser):
|
|||||||
prev = None
|
prev = None
|
||||||
for line in self.revreadlines():
|
for line in self.revreadlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line == 'startxref': break
|
if line == b'startxref': break
|
||||||
if line:
|
if line:
|
||||||
prev = line
|
prev = line
|
||||||
else:
|
else:
|
||||||
@@ -1916,7 +1919,7 @@ class PDFParser(PSStackParser):
|
|||||||
except PDFNoValidXRef:
|
except PDFNoValidXRef:
|
||||||
# fallback
|
# fallback
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
|
pat = re.compile(rb'^(\d+)\s+(\d+)\s+obj\b')
|
||||||
offsets = {}
|
offsets = {}
|
||||||
xref = PDFXRef()
|
xref = PDFXRef()
|
||||||
while 1:
|
while 1:
|
||||||
@@ -1924,7 +1927,7 @@ class PDFParser(PSStackParser):
|
|||||||
(pos, line) = self.nextline()
|
(pos, line) = self.nextline()
|
||||||
except PSEOF:
|
except PSEOF:
|
||||||
break
|
break
|
||||||
if line.startswith('trailer'):
|
if line.startswith(b'trailer'):
|
||||||
trailerpos = pos # remember last trailer
|
trailerpos = pos # remember last trailer
|
||||||
m = pat.match(line)
|
m = pat.match(line)
|
||||||
if not m: continue
|
if not m: continue
|
||||||
@@ -1951,7 +1954,7 @@ class PDFObjStrmParser(PDFParser):
|
|||||||
self.add_results(*self.popall())
|
self.add_results(*self.popall())
|
||||||
return
|
return
|
||||||
|
|
||||||
KEYWORD_R = KWD('R')
|
KEYWORD_R = KWD(b'R')
|
||||||
def do_keyword(self, pos, token):
|
def do_keyword(self, pos, token):
|
||||||
if token is self.KEYWORD_R:
|
if token is self.KEYWORD_R:
|
||||||
# reference to indirect object
|
# reference to indirect object
|
||||||
@@ -1994,7 +1997,7 @@ class PDFSerializer(object):
|
|||||||
def dump(self, outf):
|
def dump(self, outf):
|
||||||
self.outf = outf
|
self.outf = outf
|
||||||
self.write(self.version)
|
self.write(self.version)
|
||||||
self.write('\n%\xe2\xe3\xcf\xd3\n')
|
self.write(b'\n%\xe2\xe3\xcf\xd3\n')
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
objids = self.objids
|
objids = self.objids
|
||||||
xrefs = {}
|
xrefs = {}
|
||||||
@@ -2016,18 +2019,18 @@ class PDFSerializer(object):
|
|||||||
startxref = self.tell()
|
startxref = self.tell()
|
||||||
|
|
||||||
if not gen_xref_stm:
|
if not gen_xref_stm:
|
||||||
self.write('xref\n')
|
self.write(b'xref\n')
|
||||||
self.write('0 %d\n' % (maxobj + 1,))
|
self.write(b'0 %d\n' % (maxobj + 1,))
|
||||||
for objid in range(0, maxobj + 1):
|
for objid in range(0, maxobj + 1):
|
||||||
if objid in xrefs:
|
if objid in xrefs:
|
||||||
# force the genno to be 0
|
# force the genno to be 0
|
||||||
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
self.write(b"%010d 00000 n \n" % xrefs[objid][0])
|
||||||
else:
|
else:
|
||||||
self.write("%010d %05d f \n" % (0, 65535))
|
self.write(b"%010d %05d f \n" % (0, 65535))
|
||||||
|
|
||||||
self.write('trailer\n')
|
self.write(b'trailer\n')
|
||||||
self.serialize_object(trailer)
|
self.serialize_object(trailer)
|
||||||
self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
|
self.write(b'\nstartxref\n%d\n%%%%EOF' % startxref)
|
||||||
|
|
||||||
else: # Generate crossref stream.
|
else: # Generate crossref stream.
|
||||||
|
|
||||||
@@ -2076,7 +2079,7 @@ class PDFSerializer(object):
|
|||||||
data.append(struct.pack('>L', f2)[-fl2:])
|
data.append(struct.pack('>L', f2)[-fl2:])
|
||||||
data.append(struct.pack('>L', f3)[-fl3:])
|
data.append(struct.pack('>L', f3)[-fl3:])
|
||||||
index.extend((first, prev - first + 1))
|
index.extend((first, prev - first + 1))
|
||||||
data = zlib.compress(''.join(data))
|
data = zlib.compress(b''.join(data))
|
||||||
dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
|
dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
|
||||||
'W': [1, fl2, fl3], 'Length': len(data),
|
'W': [1, fl2, fl3], 'Length': len(data),
|
||||||
'Filter': LITERALS_FLATE_DECODE[0],
|
'Filter': LITERALS_FLATE_DECODE[0],
|
||||||
@@ -2085,7 +2088,7 @@ class PDFSerializer(object):
|
|||||||
dic['Info'] = trailer['Info']
|
dic['Info'] = trailer['Info']
|
||||||
xrefstm = PDFStream(dic, data)
|
xrefstm = PDFStream(dic, data)
|
||||||
self.serialize_indirect(maxobj, xrefstm)
|
self.serialize_indirect(maxobj, xrefstm)
|
||||||
self.write('startxref\n%d\n%%%%EOF' % startxref)
|
self.write(b'startxref\n%d\n%%%%EOF' % startxref)
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
self.outf.write(data)
|
self.outf.write(data)
|
||||||
self.last = data[-1:]
|
self.last = data[-1:]
|
||||||
@@ -2094,13 +2097,10 @@ class PDFSerializer(object):
|
|||||||
return self.outf.tell()
|
return self.outf.tell()
|
||||||
|
|
||||||
def escape_string(self, string):
|
def escape_string(self, string):
|
||||||
string = string.replace('\\', '\\\\')
|
string = string.replace(b'\\', b'\\\\')
|
||||||
string = string.replace('\n', r'\n')
|
string = string.replace(b'\n', rb'\n')
|
||||||
string = string.replace('(', r'\(')
|
string = string.replace(b'(', rb'\(')
|
||||||
string = string.replace(')', r'\)')
|
string = string.replace(b')', rb'\)')
|
||||||
# get rid of ciando id
|
|
||||||
regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
|
|
||||||
if regularexp.match(string): return ('http://www.ciando.com')
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def serialize_object(self, obj):
|
def serialize_object(self, obj):
|
||||||
@@ -2111,34 +2111,38 @@ class PDFSerializer(object):
|
|||||||
obj['Subtype'] = obj['Type']
|
obj['Subtype'] = obj['Type']
|
||||||
del obj['Type']
|
del obj['Type']
|
||||||
# end - hope this doesn't have bad effects
|
# end - hope this doesn't have bad effects
|
||||||
self.write('<<')
|
self.write(b'<<')
|
||||||
for key, val in obj.items():
|
for key, val in obj.items():
|
||||||
self.write('/%s' % key)
|
self.write(str(LIT(key.encode('utf-8'))).encode('utf-8'))
|
||||||
self.serialize_object(val)
|
self.serialize_object(val)
|
||||||
self.write('>>')
|
self.write(b'>>')
|
||||||
elif isinstance(obj, list):
|
elif isinstance(obj, list):
|
||||||
self.write('[')
|
self.write(b'[')
|
||||||
for val in obj:
|
for val in obj:
|
||||||
self.serialize_object(val)
|
self.serialize_object(val)
|
||||||
self.write(']')
|
self.write(b']')
|
||||||
|
elif isinstance(obj, bytearray):
|
||||||
|
self.write(b'(%s)' % self.escape_string(obj))
|
||||||
|
elif isinstance(obj, bytes):
|
||||||
|
self.write(b'(%s)' % self.escape_string(obj))
|
||||||
elif isinstance(obj, str):
|
elif isinstance(obj, str):
|
||||||
self.write('(%s)' % self.escape_string(obj))
|
self.write(b'(%s)' % self.escape_string(obj.encode('utf-8')))
|
||||||
elif isinstance(obj, bool):
|
elif isinstance(obj, bool):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(b' ')
|
||||||
self.write(str(obj).lower())
|
self.write(str(obj).lower().encode('utf-8'))
|
||||||
elif isinstance(obj, int):
|
elif isinstance(obj, int):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(b' ')
|
||||||
self.write(str(obj))
|
self.write(str(obj).encode('utf-8'))
|
||||||
elif isinstance(obj, Decimal):
|
elif isinstance(obj, Decimal):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(b' ')
|
||||||
self.write(str(obj))
|
self.write(str(obj).encode('utf-8'))
|
||||||
elif isinstance(obj, PDFObjRef):
|
elif isinstance(obj, PDFObjRef):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(b' ')
|
||||||
self.write('%d %d R' % (obj.objid, 0))
|
self.write(b'%d %d R' % (obj.objid, 0))
|
||||||
elif isinstance(obj, PDFStream):
|
elif isinstance(obj, PDFStream):
|
||||||
### If we don't generate cross ref streams the object streams
|
### If we don't generate cross ref streams the object streams
|
||||||
### are no longer useful, as we have extracted all objects from
|
### are no longer useful, as we have extracted all objects from
|
||||||
@@ -2148,41 +2152,36 @@ class PDFSerializer(object):
|
|||||||
else:
|
else:
|
||||||
data = obj.get_decdata()
|
data = obj.get_decdata()
|
||||||
self.serialize_object(obj.dic)
|
self.serialize_object(obj.dic)
|
||||||
self.write('stream\n')
|
self.write(b'stream\n')
|
||||||
self.write(data)
|
self.write(data)
|
||||||
self.write('\nendstream')
|
self.write(b'\nendstream')
|
||||||
else:
|
else:
|
||||||
data = str(obj)
|
data = str(obj).encode('utf-8')
|
||||||
if data[0].isalnum() and self.last.isalnum():
|
if bytes([data[0]]).isalnum() and self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(b' ')
|
||||||
self.write(data)
|
self.write(data)
|
||||||
|
|
||||||
def serialize_indirect(self, objid, obj):
|
def serialize_indirect(self, objid, obj):
|
||||||
self.write('%d 0 obj' % (objid,))
|
self.write(b'%d 0 obj' % (objid,))
|
||||||
self.serialize_object(obj)
|
self.serialize_object(obj)
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write('\n')
|
self.write(b'\n')
|
||||||
self.write('endobj\n')
|
self.write(b'endobj\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath):
|
||||||
if RSA is None:
|
if RSA is None:
|
||||||
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
|
raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
|
||||||
with open(inpath, 'rb') as inf:
|
with open(inpath, 'rb') as inf:
|
||||||
#try:
|
|
||||||
serializer = PDFSerializer(inf, userkey)
|
serializer = PDFSerializer(inf, userkey)
|
||||||
#except:
|
|
||||||
# print "Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
|
|
||||||
# return 2
|
|
||||||
# hope this will fix the 'bad file descriptor' problem
|
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
try:
|
try:
|
||||||
serializer.dump(outf)
|
serializer.dump(outf)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("error writing pdf: {0}".format(e.args[0]))
|
print("error writing pdf: {0}".format(e))
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -512,6 +512,8 @@ class BinaryIonParser(object):
|
|||||||
|
|
||||||
if table is not None:
|
if table is not None:
|
||||||
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
||||||
|
if len(table.symnames) < maxid:
|
||||||
|
self.symbols.importunknown(name + "-unknown", maxid - len(table.symnames))
|
||||||
else:
|
else:
|
||||||
self.symbols.importunknown(name, maxid)
|
self.symbols.importunknown(name, maxid)
|
||||||
|
|
||||||
@@ -733,7 +735,10 @@ SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
|||||||
'com.amazon.drm.EncryptedPage@2.0',
|
'com.amazon.drm.EncryptedPage@2.0',
|
||||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
||||||
'com.amazon.drm.Compressed@1.0', 'page_index_table',
|
'com.amazon.drm.Compressed@1.0', 'page_index_table',
|
||||||
'com.amazon.drm.VoucherEnvelope@2.0', 'com.amazon.drm.VoucherEnvelope@3.0' ]
|
] + ['com.amazon.drm.VoucherEnvelope@%d.0' % n
|
||||||
|
for n in list(range(2, 29)) + [
|
||||||
|
9708, 1031, 2069, 9041, 3646,
|
||||||
|
6052, 9479, 9888, 4648, 5683]]
|
||||||
|
|
||||||
def addprottable(ion):
|
def addprottable(ion):
|
||||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||||
@@ -757,9 +762,45 @@ def pkcs7unpad(msg, blocklen):
|
|||||||
|
|
||||||
|
|
||||||
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||||
VOUCHER_VERSION_INFOS = {
|
OBFUSCATION_TABLE = {
|
||||||
2: [b'Antidisestablishmentarianism', 5],
|
"V1": (0x00, None),
|
||||||
3: [b'Floccinaucinihilipilification', 8]
|
"V2": (0x05, b'Antidisestablishmentarianism'),
|
||||||
|
"V3": (0x08, b'Floccinaucinihilipilification'),
|
||||||
|
"V4": (0x07, b'>\x14\x0c\x12\x10-\x13&\x18U\x1d\x05Rlt\x03!\x19\x1b\x13\x04]Y\x19,\t\x1b'),
|
||||||
|
"V5": (0x06, b'~\x18~\x16J\\\x18\x10\x05\x0b\x07\t\x0cZ\r|\x1c\x15\x1d\x11>,\x1b\x0e\x03"4\x1b\x01'),
|
||||||
|
"V6": (0x09, b'3h\x055\x03[^>\x19\x1c\x08\x1b\rtm4\x02Rp\x0c\x16B\n'),
|
||||||
|
"V7": (0x05, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||||
|
"V8": (0x09, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||||
|
"V9": (0x05, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||||
|
"V10": (0x07, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||||
|
"V11": (0x05, b'L=\nhVm\x07go\n6\x14\x06\x16L\r\x02\x0b\x0c\x1b\x04#p\t'),
|
||||||
|
"V12": (0x06, b',n\x1d\rl\x13\x1c\x13\x16p\x14\x07U\x0c\x1f\x19w\x16\x16\x1d5T'),
|
||||||
|
"V13": (0x07, b'I\x05\t\x08\x03r)\x01$N\x0fr3n\x0b062D\x0f\x13'),
|
||||||
|
"V14": (0x05, b"\x03\x02\x1c9\x19\x15\x15q\x1057\x08\x16\x0cF\x1b.Fw\x01\x12\x03\x13\x02\x17S'hk6"),
|
||||||
|
"V15": (0x0A, b'&,4B\x1dcI\x0bU\x03I\x07\x04\x1c\t\x05c\x07%ws\x0cj\t\x1a\x08\x0f'),
|
||||||
|
"V16": (0x0A, b'\x06\x18`h,b><\x06PqR\x02Zc\x034\n\x16\x1e\x18\x06#e'),
|
||||||
|
"V17": (0x07, b'y\r\x12\x08fw.[\x02\t\n\x13\x11\x0c\x11b\x1e8L\x10(\x13<Jx6c\x0f'),
|
||||||
|
"V18": (0x07, b'I\x0b\x0e,\x19\x1aIa\x10s\x19g\\\x1b\x11!\x18yf\x0f\t\x1d7[bSp\x03'),
|
||||||
|
"V19": (0x05, b'\n6>)N\x02\x188\x016s\x13\x14\x1b\x16jeN\n\x146\x04\x18\x1c\x0c\x19\x1f,\x02]'),
|
||||||
|
"V20": (0x08, b'_\r\x01\x12]\\\x14*\x17i\x14\r\t!\x1e,~hZ\x12jK\x17\x1e*1'),
|
||||||
|
"V21": (0x07, b'e\x1d\x19|\ty\x1di|N\x13\x0e\x04\x1bj<h\x13\x15k\x12\x08=\x1f\x16~\x13l'),
|
||||||
|
"V22": (0x08, b'?\x17yi$k7Pc\tEo\x0c\x07\x07\t\x1f,*i\x12\x0cI0\x10I\x1a?2\x04'),
|
||||||
|
"V23": (0x08, b'\x16+db\x13\x04\x18\rc%\x14\x17\x0f\x13F\x0c[\t9\x1ay\x01\x1eH'),
|
||||||
|
"V24": (0x06, b'|6\\\x1a\r\x10\nP\x07\x0fu\x1f\t,\rr`uv\\~55\x11]N'),
|
||||||
|
"V25": (0x09, b'\x07\x14w\x1e,^y\x01:\x08\x07\x1fr\tU#j\x16\x12\x1eB\x04\x16=\x06fZ\x07\x02\x06'),
|
||||||
|
"V26": (0x06, b'\x03IL\x1e"K\x1f\x0f\x1fp0\x01`X\x02z0`\x03\x0eN\x07'),
|
||||||
|
"V27": (0x07, b'Xk\x10y\x02\x18\x10\x17\x1d,\x0e\x05e\x10\x15"e\x0fh(\x06s\x1c\x08I\x0c\x1b\x0e'),
|
||||||
|
"V28": (0x0A, b'6P\x1bs\x0f\x06V.\x1cM\x14\x02\n\x1b\x07{P0:\x18zaU\x05'),
|
||||||
|
"V9708": (0x05, b'\x1diIm\x08a\x17\x1e!am\x1d\x1aQ.\x16!\x06*\}x04\x11\t\x06\x04?'),
|
||||||
|
"V1031": (0x08, b'Antidisestablishmentarianism'),
|
||||||
|
"V2069": (0x07, b'Floccinaucinihilipilification'),
|
||||||
|
"V9041": (0x06, b'>\x14\x0c\x12\x10-\x13&\x18U\x1d\x05Rlt\x03!\x19\x1b\x13\x04]Y\x19,\t\x1b'),
|
||||||
|
"V3646": (0x09, b'~\x18~\x16J\\\x18\x10\x05\x0b\x07\t\x0cZ\r|\x1c\x15\x1d\x11>,\x1b\x0e\x03"4\x1b\x01'),
|
||||||
|
"V6052": (0x05, b'3h\x055\x03[^>\x19\x1c\x08\x1b\rtm4\x02Rp\x0c\x16B\n'),
|
||||||
|
"V9479": (0x09, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||||
|
"V9888": (0x05, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||||
|
"V4648": (0x07, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||||
|
"V5683": (0x05, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -768,9 +809,7 @@ def obfuscate(secret, version):
|
|||||||
if version == 1: # v1 does not use obfuscation
|
if version == 1: # v1 does not use obfuscation
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
params = VOUCHER_VERSION_INFOS[version]
|
magic, word = OBFUSCATION_TABLE["V%d" % version]
|
||||||
word = params[0]
|
|
||||||
magic = params[1]
|
|
||||||
|
|
||||||
# extend secret so that its length is divisible by the magic number
|
# extend secret so that its length is divisible by the magic number
|
||||||
if len(secret) % magic != 0:
|
if len(secret) % magic != 0:
|
||||||
@@ -809,24 +848,30 @@ class DrmIonVoucher(object):
|
|||||||
def __init__(self, voucherenv, dsn, secret):
|
def __init__(self, voucherenv, dsn, secret):
|
||||||
self.dsn, self.secret = dsn, secret
|
self.dsn, self.secret = dsn, secret
|
||||||
|
|
||||||
|
if isinstance(dsn, str):
|
||||||
|
self.dsn = dsn.encode('ASCII')
|
||||||
|
|
||||||
|
if isinstance(secret, str):
|
||||||
|
self.secret = secret.encode('ASCII')
|
||||||
|
|
||||||
self.lockparams = []
|
self.lockparams = []
|
||||||
|
|
||||||
self.envelope = BinaryIonParser(voucherenv)
|
self.envelope = BinaryIonParser(voucherenv)
|
||||||
addprottable(self.envelope)
|
addprottable(self.envelope)
|
||||||
|
|
||||||
def decryptvoucher(self):
|
def decryptvoucher(self):
|
||||||
shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
|
shared = ("PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm).encode('ASCII')
|
||||||
|
|
||||||
self.lockparams.sort()
|
self.lockparams.sort()
|
||||||
for param in self.lockparams:
|
for param in self.lockparams:
|
||||||
if param == "ACCOUNT_SECRET":
|
if param == "ACCOUNT_SECRET":
|
||||||
shared += param + self.secret
|
shared += param.encode('ASCII') + self.secret
|
||||||
elif param == "CLIENT_ID":
|
elif param == "CLIENT_ID":
|
||||||
shared += param + self.dsn
|
shared += param.encode('ASCII') + self.dsn
|
||||||
else:
|
else:
|
||||||
_assert(False, "Unknown lock parameter: %s" % param)
|
_assert(False, "Unknown lock parameter: %s" % param)
|
||||||
|
|
||||||
sharedsecret = obfuscate(shared.encode('ASCII'), self.version)
|
sharedsecret = obfuscate(shared, self.version)
|
||||||
|
|
||||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||||
@@ -993,6 +1038,7 @@ class DrmIon(object):
|
|||||||
|
|
||||||
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
||||||
decompress = False
|
decompress = False
|
||||||
|
decrypt = True
|
||||||
ct = None
|
ct = None
|
||||||
civ = None
|
civ = None
|
||||||
self.ion.stepin()
|
self.ion.stepin()
|
||||||
@@ -1006,7 +1052,23 @@ class DrmIon(object):
|
|||||||
civ = self.ion.lobvalue()
|
civ = self.ion.lobvalue()
|
||||||
|
|
||||||
if ct is not None and civ is not None:
|
if ct is not None and civ is not None:
|
||||||
self.processpage(ct, civ, outpages, decompress)
|
self.processpage(ct, civ, outpages, decompress, decrypt)
|
||||||
|
self.ion.stepout()
|
||||||
|
|
||||||
|
elif self.ion.gettypename() in ["com.amazon.drm.PlainText@1.0", "com.amazon.drm.PlainText@2.0"]:
|
||||||
|
decompress = False
|
||||||
|
decrypt = False
|
||||||
|
plaintext = None
|
||||||
|
self.ion.stepin()
|
||||||
|
while self.ion.hasnext():
|
||||||
|
self.ion.next()
|
||||||
|
if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
|
||||||
|
decompress = True
|
||||||
|
if self.ion.getfieldname() == "data":
|
||||||
|
plaintext = self.ion.lobvalue()
|
||||||
|
|
||||||
|
if plaintext is not None:
|
||||||
|
self.processpage(plaintext, None, outpages, decompress, decrypt)
|
||||||
self.ion.stepout()
|
self.ion.stepout()
|
||||||
|
|
||||||
self.ion.stepout()
|
self.ion.stepout()
|
||||||
@@ -1017,9 +1079,12 @@ class DrmIon(object):
|
|||||||
def print_(self, lst):
|
def print_(self, lst):
|
||||||
self.ion.print_(lst)
|
self.ion.print_(lst)
|
||||||
|
|
||||||
def processpage(self, ct, civ, outpages, decompress):
|
def processpage(self, ct, civ, outpages, decompress, decrypt):
|
||||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
if decrypt:
|
||||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||||
|
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
||||||
|
else:
|
||||||
|
msg = ct
|
||||||
|
|
||||||
if not decompress:
|
if not decompress:
|
||||||
outpages.write(msg)
|
outpages.write(msg)
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ def unescape(text):
|
|||||||
else:
|
else:
|
||||||
# named entity
|
# named entity
|
||||||
try:
|
try:
|
||||||
text = chr(htmlentitydefs.name2codepoint[text[1:-1]])
|
text = chr(html.entities.name2codepoint[text[1:-1]])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return text # leave as is
|
return text # leave as is
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
# Engine to remove drm from Kindle KFX ebooks
|
# Engine to remove drm from Kindle KFX ebooks
|
||||||
|
|
||||||
# 2.0 - Python 3 for calibre 5.0
|
# 2.0 - Python 3 for calibre 5.0
|
||||||
|
# 2.1 - Some fixes for debugging
|
||||||
|
# 2.1.1 - Whitespace!
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import traceback
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -65,6 +68,9 @@ class KFXZipBook:
|
|||||||
print("Decrypting KFX DRM voucher: {0}".format(info.filename))
|
print("Decrypting KFX DRM voucher: {0}".format(info.filename))
|
||||||
|
|
||||||
for pid in [''] + totalpids:
|
for pid in [''] + totalpids:
|
||||||
|
# Belt and braces. PIDs should be unicode strings, but just in case...
|
||||||
|
if isinstance(pid, bytes):
|
||||||
|
pid = pid.decode('ascii')
|
||||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
||||||
if len(pid) == dsn_len + secret_len:
|
if len(pid) == dsn_len + secret_len:
|
||||||
break # split pid into DSN and account secret
|
break # split pid into DSN and account secret
|
||||||
@@ -77,6 +83,7 @@ class KFXZipBook:
|
|||||||
voucher.decryptvoucher()
|
voucher.decryptvoucher()
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise Exception("Failed to decrypt KFX DRM voucher with any key")
|
raise Exception("Failed to decrypt KFX DRM voucher with any key")
|
||||||
|
|||||||
@@ -174,14 +174,14 @@ def pidFromSerial(s, l):
|
|||||||
|
|
||||||
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
||||||
def getKindlePids(rec209, token, serialnum):
|
def getKindlePids(rec209, token, serialnum):
|
||||||
|
if isinstance(serialnum,str):
|
||||||
|
serialnum = serialnum.encode('utf-8')
|
||||||
|
|
||||||
if rec209 is None:
|
if rec209 is None:
|
||||||
return [serialnum]
|
return [serialnum]
|
||||||
|
|
||||||
pids=[]
|
pids=[]
|
||||||
|
|
||||||
if isinstance(serialnum,str):
|
|
||||||
serialnum = serialnum.encode('utf-8')
|
|
||||||
|
|
||||||
# Compute book PID
|
# Compute book PID
|
||||||
pidHash = SHA1(serialnum+rec209+token)
|
pidHash = SHA1(serialnum+rec209+token)
|
||||||
bookPID = encodePID(pidHash)
|
bookPID = encodePID(pidHash)
|
||||||
@@ -209,7 +209,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens'])
|
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens'])
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
kindleAccountToken=""
|
kindleAccountToken = b''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -296,14 +296,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
|
|||||||
|
|
||||||
for kDatabase in kDatabases:
|
for kDatabase in kDatabases:
|
||||||
try:
|
try:
|
||||||
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
pidlst.extend(map(bytes,getK4Pids(md1, md2, kDatabase)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
print("Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
for serialnum in serials:
|
for serialnum in serials:
|
||||||
try:
|
try:
|
||||||
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
pidlst.extend(map(bytes,getKindlePids(md1, md2, serialnum)))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
print("Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|||||||
36
DeDRM_plugin/mobidedrm.py
Normal file → Executable file
36
DeDRM_plugin/mobidedrm.py
Normal file → Executable file
@@ -191,19 +191,21 @@ def PC1(key, src, decryption=True):
|
|||||||
dst+=bytes([curByte])
|
dst+=bytes([curByte])
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
|
# accepts unicode returns unicode
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
crc = (~binascii.crc32(s.encode('utf-8'),-1))&0xFFFFFFFF
|
||||||
crc = crc ^ (crc >> 16)
|
crc = crc ^ (crc >> 16)
|
||||||
res = s
|
res = s
|
||||||
l = len(letters)
|
l = len(letters)
|
||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += letters[pos%l].encode('ascii')
|
res += letters[pos%l]
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# expects bytearray
|
||||||
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
def getSizeOfTrailingDataEntry(ptr, size):
|
def getSizeOfTrailingDataEntry(ptr, size):
|
||||||
bitpos, result = 0, 0
|
bitpos, result = 0, 0
|
||||||
@@ -282,7 +284,7 @@ class MobiBook:
|
|||||||
self.mobi_codepage = 1252
|
self.mobi_codepage = 1252
|
||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
|
|
||||||
if self.magic == 'TEXtREAd':
|
if self.magic == b'TEXtREAd':
|
||||||
print("PalmDoc format book detected.")
|
print("PalmDoc format book detected.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -301,7 +303,7 @@ class MobiBook:
|
|||||||
# if exth region exists parse it for metadata array
|
# if exth region exists parse it for metadata array
|
||||||
try:
|
try:
|
||||||
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
|
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
|
||||||
exth = ''
|
exth = b''
|
||||||
if exth_flag & 0x40:
|
if exth_flag & 0x40:
|
||||||
exth = self.sect[16 + self.mobi_length:]
|
exth = self.sect[16 + self.mobi_length:]
|
||||||
if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
|
if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
|
||||||
@@ -323,12 +325,13 @@ class MobiBook:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
|
print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
|
||||||
|
|
||||||
|
#returns unicode
|
||||||
def getBookTitle(self):
|
def getBookTitle(self):
|
||||||
codec_map = {
|
codec_map = {
|
||||||
1252 : 'windows-1252',
|
1252 : 'windows-1252',
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}
|
}
|
||||||
title = ''
|
title = b''
|
||||||
codec = 'windows-1252'
|
codec = 'windows-1252'
|
||||||
if self.magic == b'BOOKMOBI':
|
if self.magic == b'BOOKMOBI':
|
||||||
if 503 in self.meta_array:
|
if 503 in self.meta_array:
|
||||||
@@ -339,7 +342,7 @@ class MobiBook:
|
|||||||
title = self.sect[toff:tend]
|
title = self.sect[toff:tend]
|
||||||
if self.mobi_codepage in codec_map.keys():
|
if self.mobi_codepage in codec_map.keys():
|
||||||
codec = codec_map[self.mobi_codepage]
|
codec = codec_map[self.mobi_codepage]
|
||||||
if title == '':
|
if title == b'':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split(b'\0')[0]
|
title = title.split(b'\0')[0]
|
||||||
return title.decode(codec)
|
return title.decode(codec)
|
||||||
@@ -355,13 +358,15 @@ class MobiBook:
|
|||||||
# if that key exists in the meta_array, append its contents to the token
|
# if that key exists in the meta_array, append its contents to the token
|
||||||
for i in range(0,len(data),5):
|
for i in range(0,len(data),5):
|
||||||
val, = struct.unpack('>I',data[i+1:i+5])
|
val, = struct.unpack('>I',data[i+1:i+5])
|
||||||
sval = self.meta_array.get(val,'')
|
sval = self.meta_array.get(val,b'')
|
||||||
token += sval
|
token += sval
|
||||||
return rec209, token
|
return rec209, token
|
||||||
|
|
||||||
|
# new must be byte array
|
||||||
def patch(self, off, new):
|
def patch(self, off, new):
|
||||||
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
||||||
|
|
||||||
|
# new must be byte array
|
||||||
def patchSection(self, section, new, in_off = 0):
|
def patchSection(self, section, new, in_off = 0):
|
||||||
if (section + 1 == self.num_sections):
|
if (section + 1 == self.num_sections):
|
||||||
endoff = len(self.data_file)
|
endoff = len(self.data_file)
|
||||||
@@ -371,12 +376,12 @@ class MobiBook:
|
|||||||
assert off + in_off + len(new) <= endoff
|
assert off + in_off + len(new) <= endoff
|
||||||
self.patch(off + in_off, new)
|
self.patch(off + in_off, new)
|
||||||
|
|
||||||
|
# pids in pidlist must be unicode, returned key is byte array, pid is unicode
|
||||||
def parseDRM(self, data, count, pidlist):
|
def parseDRM(self, data, count, pidlist):
|
||||||
found_key = None
|
found_key = None
|
||||||
keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
bigpid = pid.ljust(16,b'\0')
|
bigpid = pid.encode('utf-8').ljust(16,b'\0')
|
||||||
bigpid = bigpid
|
|
||||||
temp_key = PC1(keyvec1, bigpid, False)
|
temp_key = PC1(keyvec1, bigpid, False)
|
||||||
temp_key_sum = sum(temp_key) & 0xff
|
temp_key_sum = sum(temp_key) & 0xff
|
||||||
found_key = None
|
found_key = None
|
||||||
@@ -424,6 +429,7 @@ class MobiBook:
|
|||||||
return ".azw3"
|
return ".azw3"
|
||||||
return ".mobi"
|
return ".mobi"
|
||||||
|
|
||||||
|
# pids in pidlist may be unicode or bytearrays or bytes
|
||||||
def processBook(self, pidlist):
|
def processBook(self, pidlist):
|
||||||
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
||||||
print("Crypto Type is: {0:d}".format(crypto_type))
|
print("Crypto Type is: {0:d}".format(crypto_type))
|
||||||
@@ -445,6 +451,8 @@ class MobiBook:
|
|||||||
goodpids = []
|
goodpids = []
|
||||||
# print("DEBUG ==== pidlist = ", pidlist)
|
# print("DEBUG ==== pidlist = ", pidlist)
|
||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
|
if isinstance(pid,(bytearray,bytes)):
|
||||||
|
pid = pid.decode('utf-8')
|
||||||
if len(pid)==10:
|
if len(pid)==10:
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
||||||
@@ -457,8 +465,8 @@ class MobiBook:
|
|||||||
# print("======= DEBUG good pids = ", goodpids)
|
# print("======= DEBUG good pids = ", goodpids)
|
||||||
|
|
||||||
if self.crypto_type == 1:
|
if self.crypto_type == 1:
|
||||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
t1_keyvec = b'QDCVEPMU675RUBSZ'
|
||||||
if self.magic == 'TEXtREAd':
|
if self.magic == b'TEXtREAd':
|
||||||
bookkey_data = self.sect[0x0E:0x0E+16]
|
bookkey_data = self.sect[0x0E:0x0E+16]
|
||||||
elif self.mobi_version < 0:
|
elif self.mobi_version < 0:
|
||||||
bookkey_data = self.sect[0x90:0x90+16]
|
bookkey_data = self.sect[0x90:0x90+16]
|
||||||
@@ -473,7 +481,7 @@ class MobiBook:
|
|||||||
raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
||||||
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
if not found_key:
|
||||||
raise DrmException("No key found in {0:d} keys tried.".format(len(goodpids)))
|
raise DrmException("No key found in {0:d} PIDs tried.".format(len(goodpids)))
|
||||||
# kill the drm keys
|
# kill the drm keys
|
||||||
self.patchSection(0, b'\0' * drm_size, drm_ptr)
|
self.patchSection(0, b'\0' * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
@@ -509,6 +517,7 @@ class MobiBook:
|
|||||||
print("done")
|
print("done")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# pids in pidlist must be unicode
|
||||||
def getUnencryptedBook(infile,pidlist):
|
def getUnencryptedBook(infile,pidlist):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException("Input File Not Found.")
|
raise DrmException("Input File Not Found.")
|
||||||
@@ -530,8 +539,7 @@ def cli_main():
|
|||||||
infile = argv[1]
|
infile = argv[1]
|
||||||
outfile = argv[2]
|
outfile = argv[2]
|
||||||
if len(argv) == 4:
|
if len(argv) == 4:
|
||||||
# convert from unicode to bytearray before splitting.
|
pidlist = argv[3].split(',')
|
||||||
pidlist = argv[3].encode('utf-8').split(b',')
|
|
||||||
else:
|
else:
|
||||||
pidlist = []
|
pidlist = []
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -66,21 +66,21 @@ class fixZip:
|
|||||||
|
|
||||||
def uncompress(self, cmpdata):
|
def uncompress(self, cmpdata):
|
||||||
dc = zlib.decompressobj(-15)
|
dc = zlib.decompressobj(-15)
|
||||||
data = ''
|
data = b''
|
||||||
while len(cmpdata) > 0:
|
while len(cmpdata) > 0:
|
||||||
if len(cmpdata) > _MAX_SIZE :
|
if len(cmpdata) > _MAX_SIZE :
|
||||||
newdata = cmpdata[0:_MAX_SIZE]
|
newdata = cmpdata[0:_MAX_SIZE]
|
||||||
cmpdata = cmpdata[_MAX_SIZE:]
|
cmpdata = cmpdata[_MAX_SIZE:]
|
||||||
else:
|
else:
|
||||||
newdata = cmpdata
|
newdata = cmpdata
|
||||||
cmpdata = ''
|
cmpdata = b''
|
||||||
newdata = dc.decompress(newdata)
|
newdata = dc.decompress(newdata)
|
||||||
unprocessed = dc.unconsumed_tail
|
unprocessed = dc.unconsumed_tail
|
||||||
if len(unprocessed) == 0:
|
if len(unprocessed) == 0:
|
||||||
newdata += dc.flush()
|
newdata += dc.flush()
|
||||||
data += newdata
|
data += newdata
|
||||||
cmpdata += unprocessed
|
cmpdata += unprocessed
|
||||||
unprocessed = ''
|
unprocessed = b''
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def getfiledata(self, zi):
|
def getfiledata(self, zi):
|
||||||
@@ -123,7 +123,7 @@ class fixZip:
|
|||||||
mimeinfo.internal_attr = 1 # text file
|
mimeinfo.internal_attr = 1 # text file
|
||||||
try:
|
try:
|
||||||
# if the mimetype is present, get its info, including time-stamp
|
# if the mimetype is present, get its info, including time-stamp
|
||||||
oldmimeinfo = self.inzip.getinfo('mimetype')
|
oldmimeinfo = self.inzip.getinfo(b'mimetype')
|
||||||
# copy across useful fields
|
# copy across useful fields
|
||||||
mimeinfo.date_time = oldmimeinfo.date_time
|
mimeinfo.date_time = oldmimeinfo.date_time
|
||||||
mimeinfo.comment = oldmimeinfo.comment
|
mimeinfo.comment = oldmimeinfo.comment
|
||||||
@@ -137,7 +137,7 @@ class fixZip:
|
|||||||
|
|
||||||
# write the rest of the files
|
# write the rest of the files
|
||||||
for zinfo in self.inzip.infolist():
|
for zinfo in self.inzip.infolist():
|
||||||
if zinfo.filename != "mimetype" or self.ztype != 'epub':
|
if zinfo.filename != b"mimetype" or self.ztype != 'epub':
|
||||||
data = None
|
data = None
|
||||||
try:
|
try:
|
||||||
data = self.inzip.read(zinfo.filename)
|
data = self.inzip.read(zinfo.filename)
|
||||||
@@ -153,6 +153,7 @@ class fixZip:
|
|||||||
nzinfo.internal_attr=zinfo.internal_attr
|
nzinfo.internal_attr=zinfo.internal_attr
|
||||||
nzinfo.external_attr=zinfo.external_attr
|
nzinfo.external_attr=zinfo.external_attr
|
||||||
nzinfo.create_system=zinfo.create_system
|
nzinfo.create_system=zinfo.create_system
|
||||||
|
nzinfo.flag_bits = zinfo.flag_bits & 0x800 # preserve UTF-8 flag
|
||||||
self.outzip.writestr(nzinfo,data)
|
self.outzip.writestr(nzinfo,data)
|
||||||
|
|
||||||
self.bzf.close()
|
self.bzf.close()
|
||||||
|
|||||||
8
FAQs.md
8
FAQs.md
@@ -27,12 +27,12 @@ Verify the one of the following cryptographic hash values, using software of you
|
|||||||
#### Kindle for PC `KindleForPC-installer-1.17.44170.exe`:
|
#### Kindle for PC `KindleForPC-installer-1.17.44170.exe`:
|
||||||
* MD-5: 53F793B562F4823721AA47D7DE099869
|
* MD-5: 53F793B562F4823721AA47D7DE099869
|
||||||
* SHA-1: 73C404D719F0DD8D4AE1C2C96612B095D6C86255
|
* SHA-1: 73C404D719F0DD8D4AE1C2C96612B095D6C86255
|
||||||
* SHA-256: 14E0F0053F1276C0C7C446892DC170344F707FBFE99B695176 2C120144163200
|
* SHA-256: 14E0F0053F1276C0C7C446892DC170344F707FBFE99B6951762C120144163200
|
||||||
|
|
||||||
#### Kindle for Mac `KindleForMac-44182.dmg`:
|
#### Kindle for Mac `KindleForMac-44182.dmg`:
|
||||||
* MD-5: E7E36D5369E1F3CF1D28E5D9115DF15F
|
* MD-5: E7E36D5369E1F3CF1D28E5D9115DF15F
|
||||||
* SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
* SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
||||||
* SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E 8A9E1D19EAE2AC.
|
* SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E8A9E1D19EAE2AC
|
||||||
|
|
||||||
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the newer installation. You'll also need to delete the KFX folders from your My Kindle Content folder. You may also need to take further action to prevent an auto update. The simplest wayis to find the 'updates' folder and replace it with a file. See [this thread] (http://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead for a Script to do this on a PC. On a Mac you can find the folder at ~/Library/Application Support/Kindle/ just delete the folder 'updates' and save a blank text file called 'updates' in its place.
|
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the newer installation. You'll also need to delete the KFX folders from your My Kindle Content folder. You may also need to take further action to prevent an auto update. The simplest wayis to find the 'updates' folder and replace it with a file. See [this thread] (http://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead for a Script to do this on a PC. On a Mac you can find the folder at ~/Library/Application Support/Kindle/ just delete the folder 'updates' and save a blank text file called 'updates' in its place.
|
||||||
|
|
||||||
@@ -51,7 +51,9 @@ Mac Note: If the chmod command fails with a permission error try again using `su
|
|||||||
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book. Note that books soudl be downoad by right-click and 'Download', not by just opening the book. Recent (1.25+) versions of Kindle for Mac/PC may convert KF8 files to a new format that is not supported by these tools when the book is opened for reading.
|
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book. Note that books soudl be downoad by right-click and 'Download', not by just opening the book. Recent (1.25+) versions of Kindle for Mac/PC may convert KF8 files to a new format that is not supported by these tools when the book is opened for reading.
|
||||||
|
|
||||||
#### Decrypting KFX
|
#### Decrypting KFX
|
||||||
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for Mac/PC version 1.26 or earlier (version later than 1.26 use a new encryption scheme for KFX files). In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread.
|
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for Mac/PC. In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread.
|
||||||
|
|
||||||
|
It's quite possible that Amazon will update their KFX DeDRM to prevent DRM removal from KFX books again. So Remove DRM as soon as possible!
|
||||||
|
|
||||||
#### Thanks
|
#### Thanks
|
||||||
Thanks to jhowell for his investigations into KFX format and the KFX Input plugin. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
Thanks to jhowell for his investigations into KFX format and the KFX Input plugin. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
print_function)
|
print_function)
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '6.7.0'
|
__version__ = '7.2.1'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
@@ -20,7 +20,7 @@ except NameError:
|
|||||||
PLUGIN_NAME = 'Obok DeDRM'
|
PLUGIN_NAME = 'Obok DeDRM'
|
||||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||||
PLUGIN_VERSION_TUPLE = (6, 7, 0)
|
PLUGIN_VERSION_TUPLE = (7, 2, 1)
|
||||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||||
PLUGIN_AUTHORS = 'Anon'
|
PLUGIN_AUTHORS = 'Anon'
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
# We will write the help file out every time, in case the user upgrades the plugin zip
|
# We will write the help file out every time, in case the user upgrades the plugin zip
|
||||||
# and there is a newer help file contained within it.
|
# and there is a newer help file contained within it.
|
||||||
file_path = os.path.join(config_dir, 'plugins', HELPFILE_NAME)
|
file_path = os.path.join(config_dir, 'plugins', HELPFILE_NAME)
|
||||||
file_data = self.load_resources(HELPFILE_NAME)[HELPFILE_NAME]
|
file_data = self.load_resources(HELPFILE_NAME)[HELPFILE_NAME].decode('utf-8')
|
||||||
with open(file_path,'w') as f:
|
with open(file_path,'w') as f:
|
||||||
f.write(file_data)
|
f.write(file_data)
|
||||||
return file_path
|
return file_path
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Version 4.1.0 February 2021
|
||||||
|
# Add detection for Kobo directory location on Linux
|
||||||
|
|
||||||
# Version 4.0.0 September 2020
|
# Version 4.0.0 September 2020
|
||||||
# Python 3.0
|
# Python 3.0
|
||||||
#
|
#
|
||||||
@@ -365,9 +368,33 @@ class KoboLibrary(object):
|
|||||||
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
|
||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
||||||
#elif linux_path != None:
|
elif sys.platform.startswith('linux'):
|
||||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
|
||||||
# self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
|
#sets ~/.config/calibre as the location to store the kobodir location info file and creates this directory if necessary
|
||||||
|
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre")
|
||||||
|
if not os.path.isdir(kobodir_cache_dir):
|
||||||
|
os.mkdir(kobodir_cache_dir)
|
||||||
|
|
||||||
|
#appends the name of the file we're storing the kobodir location info to the above path
|
||||||
|
kobodir_cache_file = str(kobodir_cache_dir) + "/" + "kobo location"
|
||||||
|
|
||||||
|
"""if the above file does not exist, recursively searches from the root
|
||||||
|
of the filesystem until kobodir is found and stores the location of kobodir
|
||||||
|
in that file so this loop can be skipped in the future"""
|
||||||
|
original_stdout = sys.stdout
|
||||||
|
if not os.path.isfile(kobodir_cache_file):
|
||||||
|
for root, dirs, files in os.walk('/'):
|
||||||
|
for file in files:
|
||||||
|
if file == 'Kobo.sqlite':
|
||||||
|
kobo_linux_path = str(root)
|
||||||
|
with open(kobodir_cache_file, 'w') as f:
|
||||||
|
sys.stdout = f
|
||||||
|
print(kobo_linux_path, end='')
|
||||||
|
sys.stdout = original_stdout
|
||||||
|
|
||||||
|
f = open(kobodir_cache_file, 'r' )
|
||||||
|
self.kobodir = f.read()
|
||||||
|
|
||||||
# desktop versions use Kobo.sqlite
|
# desktop versions use Kobo.sqlite
|
||||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
||||||
# check for existence of file
|
# check for existence of file
|
||||||
@@ -443,15 +470,15 @@ class KoboLibrary(object):
|
|||||||
"""The list of all MAC addresses on this machine."""
|
"""The list of all MAC addresses on this machine."""
|
||||||
macaddrs = []
|
macaddrs = []
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s?(' + '[0-9a-f]{2}[:\-]' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
output = subprocess.Popen('wmic nic where PhysicalAdapter=True get MACAddress', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
||||||
for line in output:
|
for line in output:
|
||||||
m = c.search(line)
|
m = c.search(line)
|
||||||
if m:
|
if m:
|
||||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
output = subprocess.check_output('/sbin/ifconfig -a', shell=True, encoding='utf-8')
|
||||||
matches = c.findall(output)
|
matches = c.findall(output)
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:{0}".format(m[0])
|
# print "m:{0}".format(m[0])
|
||||||
|
|||||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2014-11-17 12:51+0100\n"
|
"POT-Creation-Date: 2014-11-17 12:51+0100\n"
|
||||||
"PO-Revision-Date: 2020-02-02 09:18+0100\n"
|
"PO-Revision-Date: 2021-01-19 12:20+0100\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -16,14 +16,14 @@ msgstr ""
|
|||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"X-Generator: Poedit 2.2.4\n"
|
"X-Generator: Poedit 2.4.2\n"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:80
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:80
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>No books found in Kobo Library\n"
|
"<p>No books found in Kobo Library\n"
|
||||||
"Are you sure it's installed\\configured\\synchronized?"
|
"Are you sure it's installed\\configured\\synchronized?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p>Inga böcker finns i Kobo-bibliotek\n"
|
"<p>Inga böcker hittades i Kobo-bibliotek\n"
|
||||||
"Är du säker på att den är installerad\\konfigurerad\\synkroniserad?"
|
"Är du säker på att den är installerad\\konfigurerad\\synkroniserad?"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
|
||||||
@@ -36,7 +36,7 @@ msgstr "Problem med att hämta nycklar med nyare obok-metod."
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
|
||||||
msgid "Found {0} possible keys to try."
|
msgid "Found {0} possible keys to try."
|
||||||
msgstr "Hittade {0} möjliga nycklar att pröva med."
|
msgstr "Hittade {0} möjliga nycklar att försöka med."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
|
||||||
msgid "<p>No userkeys found to decrypt books with. No point in proceeding."
|
msgid "<p>No userkeys found to decrypt books with. No point in proceeding."
|
||||||
@@ -46,7 +46,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
|
||||||
msgid "{} - Decryption canceled by user."
|
msgid "{} - Decryption canceled by user."
|
||||||
msgstr "{} - Dekryptering avbryts av användaren."
|
msgstr "{} - Dekryptering avbröts av användaren."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
|
||||||
msgid "{} - \"Add books\" canceled by user."
|
msgid "{} - \"Add books\" canceled by user."
|
||||||
@@ -87,14 +87,14 @@ msgstr "dubblett upptäcktes"
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
|
||||||
msgid "{0} - Successfully added EPUB format to existing {1}"
|
msgid "{0} - Successfully added EPUB format to existing {1}"
|
||||||
msgstr "{0} - Lade till EPUB-format till befintliga {1}"
|
msgstr "{0} - EPUB-formatet har lagts till i befintliga {1}"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
|
||||||
msgid ""
|
msgid ""
|
||||||
"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
|
"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"{0} - Fel vid tillägg av EPUB-format till befintligt {1}. Det här borde inte "
|
"{0} - Fel vid tilläggning av EPUB-formatet till befintliga {1}. Detta borde "
|
||||||
"hända."
|
"verkligen inte hända."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
|
||||||
msgid "{} - \"Insert formats\" canceled by user."
|
msgid "{} - \"Insert formats\" canceled by user."
|
||||||
@@ -103,44 +103,44 @@ msgstr "{} - \"Infoga format\" avbröts av användaren."
|
|||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p><b>{0}</b> EPUB{2} successfully added to library.<br /><br /><b>{1}</b> "
|
"<p><b>{0}</b> EPUB{2} successfully added to library.<br /><br /><b>{1}</b> "
|
||||||
msgstr "<p><b>{0}</b> EPUB{2} lades till bibliotek.<br /><br /><b>{1}</b> "
|
msgstr "<p><b>{0}</b> EPUB{2} har lagts till i bibliotek.<br /><br /><b>{1}</b> "
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
|
||||||
msgid ""
|
msgid ""
|
||||||
"not added because books with the same title/author were detected.<br /><br /"
|
"not added because books with the same title/author were detected.<br /><br /"
|
||||||
">Would you like to try and add the EPUB format{0}"
|
">Would you like to try and add the EPUB format{0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"inte tillagd eftersom böcker med samma titel/författare upptäcktes.<br/><br /"
|
"lades inte till eftersom böcker med samma titel/författare upptäcktes.<br/"
|
||||||
">Vill du försöka lägga till EPUB-formatet{0}"
|
"><br />Vill du försöka lägga till EPUB-formatet{0}"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
|
||||||
msgid ""
|
msgid ""
|
||||||
" to those existing entries?<br /><br />NOTE: no pre-existing EPUBs will be "
|
" to those existing entries?<br /><br />NOTE: no pre-existing EPUBs will be "
|
||||||
"overwritten."
|
"overwritten."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
" till dessa befintliga poster?<br /><br />OBS: inga befintliga EPUB:er "
|
" till dessa befintliga poster?<br /><br />OBS: inga befintliga EPUB:er kommer "
|
||||||
"kommer att skrivas över."
|
"att skrivas över."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
|
||||||
msgid ""
|
msgid ""
|
||||||
"{0} -- not added because of {1} in your library.\n"
|
"{0} -- not added because of {1} in your library.\n"
|
||||||
"\n"
|
"\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"{0} -- inte tillagd på grund av {1} i ditt bibliotek.\n"
|
"{0} -- lades inte till på grund av {1} i ditt bibliotek.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
|
||||||
msgid "<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />"
|
msgid "<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p><b>{0}</b> -- inte tillagd på grund av {1} i ditt bibliotek.<br /><br />"
|
"<p><b>{0}</b> -- lades inte till på grund av {1} i ditt bibliotek.<br /><br />"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
|
||||||
msgid ""
|
msgid ""
|
||||||
"Would you like to try and add the EPUB format to an available calibre "
|
"Would you like to try and add the EPUB format to an available calibre "
|
||||||
"duplicate?<br /><br />"
|
"duplicate?<br /><br />"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vill du försöka lägga till EPUB-formatet till en tillgänglig calibre-"
|
"Vill du försöka lägga till EPUB-formatet till en tillgänglig calibre-dubblett?"
|
||||||
"dubblett?<br /><br />"
|
"<br /><br />"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
|
||||||
msgid "NOTE: no pre-existing EPUB will be overwritten."
|
msgid "NOTE: no pre-existing EPUB will be overwritten."
|
||||||
@@ -148,23 +148,23 @@ msgstr "OBS: ingen befintlig EPUB kommer att skrivas över."
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
|
||||||
msgid "Trying key: "
|
msgid "Trying key: "
|
||||||
msgstr "Prövar nyckel: "
|
msgstr "Försöker med nyckel: "
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
|
||||||
msgid "Decryption failed, trying next key."
|
msgid "Decryption failed, trying next key."
|
||||||
msgstr "Det gick inte att dekryptera, prövar nästa nyckel."
|
msgstr "Dekryptering misslyckades, försöker med nästa nyckel."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
|
||||||
msgid "Unknown Error decrypting, trying next key.."
|
msgid "Unknown Error decrypting, trying next key.."
|
||||||
msgstr "Okänt fel dekryptering, prövar nästa nyckel.."
|
msgstr "Okänt fel vid dekryptering, försöker med nästa nyckel.."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>All selected Kobo books added as new calibre books or inserted into "
|
"<p>All selected Kobo books added as new calibre books or inserted into "
|
||||||
"existing calibre ebooks.<br /><br />No issues."
|
"existing calibre ebooks.<br /><br />No issues."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p>Alla valda Kobo-böcker läggs till som nya calibre-böcker eller infogas i "
|
"<p>Alla valda Kobo-böcker har lagts till som nya calibre-böcker eller infogats "
|
||||||
"befintliga calibre-e-böcker.<br /><br />Inga problem."
|
"i befintliga calibre-e-böcker.<br /><br />Inga problem."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
|
||||||
msgid "<p>{0} successfully added."
|
msgid "<p>{0} successfully added."
|
||||||
@@ -192,15 +192,14 @@ msgstr "<p><b>Nya böcker skapade:</b> {}</p>\n"
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:418
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:418
|
||||||
msgid "<p><b>Duplicates that weren't added:</b> {}</p>\n"
|
msgid "<p><b>Duplicates that weren't added:</b> {}</p>\n"
|
||||||
msgstr "<p><b>Dubbletter som inte tillsattes:</b> {}</p>\n"
|
msgstr "<p><b>Dubbletter som inte har lagts till:</b> {}</p>\n"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:426
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:426
|
||||||
msgid "<p><b>Book imports cancelled by user:</b> {}</p>\n"
|
msgid "<p><b>Book imports cancelled by user:</b> {}</p>\n"
|
||||||
msgstr "<p><b>Bokimport avbröts av användaren:</b> {}</p>\n"
|
msgstr "<p><b>Bokimport avbröts av användaren:</b> {}</p>\n"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:428
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:428
|
||||||
msgid ""
|
msgid "<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n"
|
||||||
"<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p><b>Nya EPUB-format infogade i befintliga calibre-böcker:</b> {0}</p>\n"
|
"<p><b>Nya EPUB-format infogade i befintliga calibre-böcker:</b> {0}</p>\n"
|
||||||
|
|
||||||
@@ -208,8 +207,7 @@ msgstr ""
|
|||||||
msgid ""
|
msgid ""
|
||||||
"<p><b>EPUB formats NOT inserted into existing calibre books:</b> {}<br />\n"
|
"<p><b>EPUB formats NOT inserted into existing calibre books:</b> {}<br />\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p><b>EPUB-format som INTE infogats i befintliga calibre-böcker:</b> {}<br /"
|
"<p><b>EPUB-format som INTE infogats i befintliga calibre-böcker:</b> {}<br />\n"
|
||||||
">\n"
|
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -221,7 +219,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
|
||||||
msgid "<p><b>Format imports cancelled by user:</b> {}</p>\n"
|
msgid "<p><b>Format imports cancelled by user:</b> {}</p>\n"
|
||||||
msgstr "<p><b>Format-import avbröts av användaren:</b> {}</p>\n"
|
msgstr "<p><b>Format-importen avbröts av användaren:</b> {}</p>\n"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:458
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:458
|
||||||
msgid "Unknown Book Title"
|
msgid "Unknown Book Title"
|
||||||
@@ -233,11 +231,11 @@ msgstr "den kunde inte dekrypteras."
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:462
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:462
|
||||||
msgid ""
|
msgid ""
|
||||||
"user CHOSE not to insert the new EPUB format, or all existing calibre "
|
"user CHOSE not to insert the new EPUB format, or all existing calibre entries "
|
||||||
"entries HAD an EPUB format already."
|
"HAD an EPUB format already."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"användaren VALDE att inte infoga det nya EPUB-formatet, eller alla "
|
"användaren VALDE att inte infoga det nya EPUB-formatet, eller alla befintliga "
|
||||||
"befintliga calibre-poster hade redan ett EPUB-format."
|
"calibre-poster hade redan ett EPUB-format."
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:464
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:464
|
||||||
msgid "of unknown reasons. Gosh I'm embarrassed!"
|
msgid "of unknown reasons. Gosh I'm embarrassed!"
|
||||||
@@ -245,7 +243,7 @@ msgstr "av okända skäl. Jag skäms!"
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:465
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:465
|
||||||
msgid "<p>{0} not added because {1}"
|
msgid "<p>{0} not added because {1}"
|
||||||
msgstr "<p>{0} inte tillagd eftersom {1}"
|
msgstr "<p>{0} lades inte till på grund av {1}"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
|
||||||
msgid "Help"
|
msgid "Help"
|
||||||
@@ -254,31 +252,31 @@ msgstr "Hjälp"
|
|||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
|
||||||
msgid "Restart required"
|
msgid "Restart required"
|
||||||
msgstr "Omstart krävs"
|
msgstr "Kräver omstart"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
|
||||||
msgid ""
|
msgid ""
|
||||||
"Title image not found - you must restart Calibre before using this plugin!"
|
"Title image not found - you must restart Calibre before using this plugin!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Titelbild hittades inte - du måste starta calibre innan du använder denna "
|
"Titelbild hittades inte - du måste starta om calibre innan du använder denna "
|
||||||
"insticksmodul!"
|
"insticksmodul!"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
|
||||||
msgid "Undefined"
|
msgid "Undefined"
|
||||||
msgstr "Obestämd"
|
msgstr "Odefinierad"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
|
||||||
msgid "When should Obok try to insert EPUBs into existing calibre entries?"
|
msgid "When should Obok try to insert EPUBs into existing calibre entries?"
|
||||||
msgstr "När ska Obok försöka infoga EPUB:er i befintliga calibre-böcker?"
|
msgstr "När ska Obok försöka infoga EPUB:er i befintliga calibre-poster?"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>Default behavior when duplicates are detected. None of the choices will "
|
"<p>Default behavior when duplicates are detected. None of the choices will "
|
||||||
"cause calibre ebooks to be overwritten"
|
"cause calibre ebooks to be overwritten"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p>Standardbeteende när dubbletter upptäcks. Inget av alternativen kommer "
|
"<p>Standardbeteende när dubbletter upptäcks. Inget av alternativen kommer att "
|
||||||
"att orsaka calibre-e-böcker att skrivas över"
|
"orsaka att calibre-e-böcker skrivs över"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
||||||
msgid "Ask"
|
msgid "Ask"
|
||||||
@@ -323,7 +321,7 @@ msgstr "Välj alla böcker med DRM."
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
|
||||||
msgid "All DRM free"
|
msgid "All DRM free"
|
||||||
msgstr "Alla DRM fria"
|
msgstr "Alla utan DRM"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
|
||||||
msgid "Select all books without DRM."
|
msgid "Select all books without DRM."
|
||||||
@@ -355,12 +353,12 @@ msgstr "Tar bort DRM från Kobo-kepubs och lägger till dem i biblioteket."
|
|||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
|
||||||
msgid "AES improper key used"
|
msgid "AES improper key used"
|
||||||
msgstr "AES felaktig nyckel används"
|
msgstr "Felaktig AES-nyckel används"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
|
||||||
msgid "Failed to initialize AES key"
|
msgid "Failed to initialize AES key"
|
||||||
msgstr "Det gick inte att initiera AES-nyckel"
|
msgstr "Misslyckades med att initiera AES-nyckel"
|
||||||
|
|
||||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
|
||||||
msgid "AES decryption failed"
|
msgid "AES decryption failed"
|
||||||
msgstr "AES dekryptering misslyckades"
|
msgstr "AES-dekryptering misslyckades"
|
||||||
|
|||||||
@@ -2290,10 +2290,13 @@ class PDFDocument(object):
|
|||||||
import win32api
|
import win32api
|
||||||
import win32security
|
import win32security
|
||||||
import win32file
|
import win32file
|
||||||
import winreg
|
|
||||||
except:
|
except:
|
||||||
raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
|
raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
|
||||||
'Download from http://sourceforge.net/projects/pywin32/files/ ')
|
'Download from http://sourceforge.net/projects/pywin32/files/ ')
|
||||||
|
try:
|
||||||
|
import winreg
|
||||||
|
except ImportError:
|
||||||
|
import _winreg as winreg
|
||||||
try:
|
try:
|
||||||
v0 = win32api.GetVolumeInformation('C:\\')
|
v0 = win32api.GetVolumeInformation('C:\\')
|
||||||
v1 = win32api.GetSystemInfo()[6]
|
v1 = win32api.GetSystemInfo()[6]
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ The individual scripts are now released as two plugins for calibre: DeDRM and Ob
|
|||||||
The DeDRM plugin handles books that use Amazon DRM, Adobe Digital Editions DRM (version 1), Barnes & Noble DRM, and some historical formats.
|
The DeDRM plugin handles books that use Amazon DRM, Adobe Digital Editions DRM (version 1), Barnes & Noble DRM, and some historical formats.
|
||||||
The Obok plugin handles Kobo DRM.
|
The Obok plugin handles Kobo DRM.
|
||||||
|
|
||||||
Users with calibre 5.x or later should use release 7.0.0b2 or later of the tools.
|
Users with calibre 5.x or later should use release 7.2.0 or later of the tools.
|
||||||
Users with calibe 4.x or earlier should use release 6.8.0 of the tools.
|
Users with calibe 4.x or earlier should use release 6.8.x of the tools.
|
||||||
|
|
||||||
Developers might be interested in forking the repository, as it contains unzipped versions of those tools that are zipped to make the changes over time easier to follow.
|
|
||||||
|
|
||||||
For the latest Amazon KFX format, users of the calibre plugin should also install the KFX Input plugin from the standard calibre plugin menu. It's also available from the MobileRead thread here: https://www.mobileread.com/forums/showthread.php?t=291290
|
For the latest Amazon KFX format, users of the calibre plugin should also install the KFX Input plugin from the standard calibre plugin menu. It's also available from the MobileRead thread here: https://www.mobileread.com/forums/showthread.php?t=291290
|
||||||
|
|
||||||
Note that DRM can only be removed from KFX format files downloaded with Kindle for PC/Mac 1.26 or earlier. Amazon changes the DRM for KFX files in Kindle for PC/Mac 1.27 and later.
|
Note that Amazon changes the DRM for KFX files frequently. What works for KFX today might not work tomorrow.
|
||||||
|
|
||||||
I welcome contributions from others to improve these tools, from expanding the range of books handled, improving key retrieval, to just general bug fixes, speed improvements and UI enhancements.
|
I welcome contributions from others to improve these tools, from expanding the range of books handled, improving key retrieval, to just general bug fixes, speed improvements and UI enhancements.
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ The tools are provided in the form of plugins for calibre. Calibre is an open so
|
|||||||
|
|
||||||
DeDRM plugin for calibre (Mac OS X, Windows)
|
DeDRM plugin for calibre (Mac OS X, Windows)
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
calibe 5.x and later are now written in Python 3, and plugins must also use Python 3. If you have calibre 5, you must use version 7.x or later of the plugins. For calibre 4.x and earlier, use version 6.8.0 of the plugins.
|
calibe 5.x and later are now written in Python 3, and plugins must also use Python 3. If you have calibre 5, you must use version 7.x or later of the plugins. For calibre 4.x and earlier, use version 6.8.x of the plugins.
|
||||||
|
|
||||||
The DeDRM plugin for calibre removes DRM from your Kindle and Adobe DRM ebooks when they are imported to calibre. Just install the DeDRM plugin (DeDRM_plugin.zip), following the instructions and configuration directions provided in the ReadMe file and the help links in the plugin's configuration dialogs.
|
The DeDRM plugin for calibre removes DRM from your Kindle and Adobe DRM ebooks when they are imported to calibre. Just install the DeDRM plugin (DeDRM_plugin.zip), following the instructions and configuration directions provided in the ReadMe file and the help links in the plugin's configuration dialogs.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user