Compare commits

..

11 Commits

Author SHA1 Message Date
Apprentice Harper
ed412bee35 Updated to inept.pdf for PC
Contributed changes for PC compatibility. Thanks, Aldo.

Update main version to 7.0.2
2021-01-03 16:01:14 +00:00
Apprentice Harper
6cee615f26 Update ineptpdf.py
Fix handling of metadata
2021-01-03 15:35:17 +00:00
Apprentice Harper
c4581b4d72 Version to 7.0.1, ineptpdf fixes
ineptpdf should now decrypt at least some Adobe PDFs
2020-12-30 12:14:04 +00:00
Apprentice Harper
f6a568bcc1 Update ineptepub.py
Handle uncompressed elements (if any) in the zip file.
2020-12-27 12:16:11 +00:00
Apprentice Harper
bf6170e613 Merge pull request #1445 from ableeker/python3
Some more fixes for ePub
2020-12-26 16:02:12 +00:00
Apprentice Harper
afcd79c0cc Merge pull request #1443 from jony0008/master
Update sv translation
2020-12-26 16:00:04 +00:00
Apprentice Harper
fdf0389936 MobiDeDRM fixes
Change handling of PIDs to cope with byte arrays or strings passed in. Also fixed handling of a very old default key format.
2020-12-26 15:58:42 +00:00
Aldo Bleeker
5599c1694b Some more fixes for ePub 2020-12-26 15:36:10 +01:00
Jony
dff90fae6f Update sv translation 2020-12-25 12:47:14 +00:00
Apprentice Harper
d33f679eae Merge pull request #1413 from ableeker/python3
Small fix to make Obok help link work.
2020-12-13 11:28:51 +00:00
Aldo Bleeker
225e74a334 Small fix to make Obok help work. 2020-12-09 17:34:24 +01:00
9 changed files with 264 additions and 245 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

View File

@@ -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.0.2'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@@ -69,8 +69,10 @@ __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.
""" """
Decrypt DRMed ebooks. Decrypt DRMed ebooks.

View File

@@ -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:
decompressed_bytes = dc.decompress(bytes)
ex = dc.decompress(b'Z') + dc.flush() ex = dc.decompress(b'Z') + dc.flush()
if ex: if ex:
bytes = bytes + ex decompressed_bytes = decompressed_bytes + ex
except:
# possibly not compressed by zip - just return bytes
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:

350
DeDRM_plugin/ineptpdf.py Normal file → Executable file
View 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?
@@ -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,7 +1426,7 @@ 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']))
@@ -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,7 +1543,7 @@ 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):
@@ -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:
@@ -1588,16 +1591,19 @@ class PDFDocument(object):
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 try:
index = bookkey.index(b'\0') + 1
bookkey = bookkey[index:] bookkey = bookkey[index:]
except ValueError:
pass
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 +1649,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 +1658,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 +1670,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 +1680,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 +1688,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 +1795,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 +1847,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 +1868,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 +1920,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 +1928,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 +1955,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 +1998,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 +2020,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 +2080,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 +2089,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 +2098,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 +2112,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 +2153,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

36
DeDRM_plugin/mobidedrm.py Normal file → Executable file
View 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:

View File

@@ -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)

View File

@@ -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

Binary file not shown.

View File

@@ -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: 2020-12-25 13:38+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,17 +36,17 @@ 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."
msgstr "" msgstr ""
"<p>Inga användarnycklar hittades för att dekryptera böcker med. Det är ingen " "<p>Inga användarnycklar hittades för att dekryptera böcker med. Ingen idé "
"idé att fortsätta." "att fortsätta."
#: 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-format 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-format till befintligt {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,15 +103,15 @@ 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 ""
@@ -126,45 +126,46 @@ 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-format 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."
msgstr "OBS: ingen befintlig EPUB kommer att skrivas över." msgstr "OBS: inga befintliga EPUB:er 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 "Det gick inte att dekryptera, 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 "
"befintliga calibre-e-böcker.<br /><br />Inga problem." "infogats 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,7 +193,7 @@ 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"
@@ -221,7 +222,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>Formatimporten 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"
@@ -237,7 +238,10 @@ msgid ""
"entries HAD an EPUB format already." "entries 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 calibre-poster hade redan ett EPUB-format." "befintliga calibre-poster hade redan ett EPUB-format.\n"
"\n"
"användaren valde att inte infoga det nya EPUB-formatet, eller alla "
"befintliga kaliberposter 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 +249,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 +258,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" "göra 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 +327,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 +359,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 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"