Compare commits

...

3 Commits

Author SHA1 Message Date
Aleksa Sarai
3b0b4c91d4 Merge ad33aea18d into e509b7d520 2023-08-03 19:56:30 +08:00
NoDRM
e509b7d520 Fix python2 issues in kgenpids and kindlekey 2023-08-03 11:26:05 +02:00
Aleksa Sarai
ad33aea18d obok: linux: improve Kobo Desktop directory searching
The Kobo Desktop program will (when running under Wine on Linux) put all
of its data in the current working directory. This means that there
may be more than one Kobo.sqlite floating around by the user, which
leads to Obok showing an outdated list of books and the user being
confused by Obok cannot find the full list of books.

Solving this completely appears to be a bit too complicated, so this
patch is a best-effort improvement for the worst cases which can be
caused by this:

 1. If the user deletes the files but Obok has already cached the path,
    previously Obok would just error out rather than trying to search
    for a new Kobo.sqlite path and updating the cache.

 2. We search $HOME before searching /, which speeds up initial usage of
    Obok in the common case (usually Kobo Desktop will be installed in
    ~/.wine/drive_c) and also ensures that we correctly preference the
    current user's Kobo Desktop installation.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2022-02-20 03:16:26 +11:00
4 changed files with 71 additions and 37 deletions

View File

@@ -101,3 +101,4 @@ This is v10.0.9, a release candidate for v10.1.0. I don't expect there to be maj
- Fix a bug where decrypting a 40-bit RC4 pdf with R=2 didn't work. - Fix a bug where decrypting a 40-bit RC4 pdf with R=2 didn't work.
- Fix a bug where decrypting a 256-bit AES pdf with V=5 didn't work. - Fix a bug where decrypting a 256-bit AES pdf with V=5 didn't work.
- Fix bugs in kgenpids.py and kindlekey.py that caused it to fail on Python 2 (#380).

View File

@@ -53,11 +53,17 @@ def SHA1(message):
def encode(data, map): def encode(data, map):
result = b'' result = b''
for char in data: for char in data:
if sys.version_info[0] == 2:
value = ord(char)
else:
value = char value = char
Q = (value ^ 0x80) // len(map) Q = (value ^ 0x80) // len(map)
R = value % len(map) R = value % len(map)
result += bytes([map[Q]])
result += bytes([map[R]]) result += bytes(bytearray([map[Q]]))
result += bytes(bytearray([map[R]]))
return result return result
# Hash the bytes in data and then encode the digest with the characters in map # Hash the bytes in data and then encode the digest with the characters in map
@@ -84,6 +90,9 @@ def decode(data,map):
def getTwoBitsFromBitField(bitField,offset): def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4 byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4) bitPosition = 6 - 2*(offset % 4)
if sys.version_info[0] == 2:
return ord(bitField[byteNumber]) >> bitPosition & 3
else:
return bitField[byteNumber] >> bitPosition & 3 return bitField[byteNumber] >> bitPosition & 3
# Returns the six bits at offset from a bit field # Returns the six bits at offset from a bit field
@@ -97,7 +106,8 @@ def encodePID(hash):
global charMap3 global charMap3
PID = b'' PID = b''
for position in range (0,8): for position in range (0,8):
PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]]) PID += bytes(bytearray([charMap3[getSixBitsFromBitField(hash,position)]]))
return PID return PID
# Encryption table used to generate the device PID # Encryption table used to generate the device PID
@@ -134,7 +144,7 @@ def generateDevicePID(table,dsn,nbRoll):
index = (index+1) %8 index = (index+1) %8
for counter in range (0,8): for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += bytes([charMap4[index]]) pidAscii += bytes(bytearray([charMap4[index]]))
return pidAscii return pidAscii
def crc32(s): def crc32(s):
@@ -150,7 +160,7 @@ def checksumPid(s):
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 += bytes([charMap4[pos%l]]) res += bytes(bytearray([charMap4[pos%l]]))
crc >>= 8 crc >>= 8
return res return res
@@ -161,6 +171,9 @@ def pidFromSerial(s, l):
crc = crc32(s) crc = crc32(s)
arr1 = [0]*l arr1 = [0]*l
for i in range(len(s)): for i in range(len(s)):
if sys.version_info[0] == 2:
arr1[i%l] ^= ord(s[i])
else:
arr1[i%l] ^= s[i] arr1[i%l] ^= s[i]
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
for i in range(l): for i in range(l):
@@ -168,7 +181,7 @@ def pidFromSerial(s, l):
pid = b"" pid = b""
for i in range(l): for i in range(l):
b = arr1[i] & 0xff b = arr1[i] & 0xff
pid += bytes([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]]) pid += bytes(bytearray([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]]))
return pid return pid

View File

@@ -115,11 +115,17 @@ def primes(n):
def encode(data, map): def encode(data, map):
result = b'' result = b''
for char in data: for char in data:
if sys.version_info[0] == 2:
value = ord(char)
else:
value = char value = char
Q = (value ^ 0x80) // len(map) Q = (value ^ 0x80) // len(map)
R = value % len(map) R = value % len(map)
result += bytes([map[Q]])
result += bytes([map[R]]) result += bytes(bytearray([map[Q]]))
result += bytes(bytearray([map[R]]))
return result return result
# Hash the bytes in data and then encode the digest with the characters in map # Hash the bytes in data and then encode the digest with the characters in map

View File

@@ -327,36 +327,50 @@ class KoboLibrary(object):
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 sys.platform.startswith('linux'): elif sys.platform.startswith('linux'):
# Since on Linux, you have to run Kobo Desktop within Wine,
# there is no guarantee where Kobo.sqlite (and the rest of
# the kobo directory) might be.
#
# It should also be noted that Kobo Desktop will store all
# of it files in the current directory where you run it,
# meaning that a user might accidentally create several
# Kobo.sqlite files which are all separate and then be
# confused why Obok can't find any of the new books. Sadly
# there isn't a trivial way to deal with this.
#sets ~/.config/calibre as the location to store the kobodir location info file and creates this directory if necessary # We cache the kobodir we find in ~/.config/calibre.
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre") kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre", "plugins", "obok")
if not os.path.isdir(kobodir_cache_dir): if not os.path.isdir(kobodir_cache_dir):
os.mkdir(kobodir_cache_dir) os.mkdir(kobodir_cache_dir)
kobodir_cache_file = os.path.join(kobodir_cache_dir, "kobo-location")
#appends the name of the file we're storing the kobodir location info to the above path try:
kobodir_cache_file = str(kobodir_cache_dir) + "/" + "kobo location" # If the cached version exists and the path does really
# contain Kobo.sqlite, use that.
with open(kobodir_cache_file, "r") as f:
cached_kobodir = f.read().strip()
assert os.path.isfile(os.path.join(cached_kobodir, "Kobo.sqlite"))
self.kobodir = cached_kobodir
except (AssertionError, FileNotFoundError):
# If there was no cached version, search the entire
# filesystem tree for a directory containing
# "Kobo.sqlite".
#
# We first search $HOME to avoid picking another user's
# Kobo.sqlite file, but then fallback to / if there was
# nothing in $HOME.
for candidate_root in (os.environ["HOME"], "/"):
for root, _, files in os.walk(candidate_root):
if "Kobo.sqlite" in files:
with open(kobodir_cache_file, "w") as f:
f.write("%s/\n" % (root,))
self.kobodir = root
break
"""if the above file does not exist, recursively searches from the root # Desktop versions use Kobo.sqlite.
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
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
if (not(os.path.isfile(kobodb))): if not os.path.isfile(kobodb):
# give up here, we haven't found anything useful # give up here, we haven't found anything useful
self.kobodir = u"" self.kobodir = u""
kobodb = u"" kobodb = u""