]> xmof Git - DeDRM.git/commitdiff
Fix ZIP attribute "external_attr" getting reset
authorNoDRM <no_drm123@protonmail.com>
Sat, 6 Aug 2022 11:53:03 +0000 (13:53 +0200)
committerNoDRM <no_drm123@protonmail.com>
Sat, 6 Aug 2022 11:53:03 +0000 (13:53 +0200)
CHANGELOG.md
DeDRM_plugin/epubfontdecrypt.py
DeDRM_plugin/epubwatermark.py
DeDRM_plugin/ineptepub.py
DeDRM_plugin/zeroedzipinfo.py [new file with mode: 0644]
DeDRM_plugin/zipfilerugged.py
DeDRM_plugin/zipfix.py

index 8f3c7f20283e2fd804b50cba3a817c96e10fe62e..783ce7568ee3b040aac7c6c92a64d3d5fbbc4858 100644 (file)
@@ -71,4 +71,5 @@ List of changes since the fork of Apprentice Harper's repository:
 
 - Fix a bug introduced with #48 that breaks DeDRM'ing on Calibre 4 (fixes #101).
 - Fix some more Calibre-6 bugs in the Obok plugin (should fix #114).
-- Fix a bug where invalid Adobe keys could cause the plugin to stop trying subsequent keys (partially fixes #109).
\ No newline at end of file
+- Fix a bug where invalid Adobe keys could cause the plugin to stop trying subsequent keys (partially fixes #109).
+- Fix DRM removal sometimes resetting the ZIP's internal "external_attr" value on Calibre 5 and newer.
index ea08175b9644169554af6f34eff777a02754b591..4baa37524bb8666904b495f8a2142774ea17a662 100644 (file)
@@ -25,6 +25,7 @@ import traceback
 import zlib
 import zipfile
 from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
+from zeroedzipinfo import ZeroedZipInfo
 from contextlib import closing
 from lxml import etree
 import itertools
@@ -298,13 +299,21 @@ def decryptFontsBook(inpath, outpath):
                         zi.internal_attr = oldzi.internal_attr
                         # external attributes are dependent on the create system, so copy both.
                         zi.external_attr = oldzi.external_attr
+                        zi.volume = oldzi.volume
                         zi.create_system = oldzi.create_system
+                        zi.create_version = oldzi.create_version
+
                         if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
                             # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
                             zi.flag_bits |= 0x800
                     except:
                         pass
 
+                    # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                    # if it's NULL, so we need a workaround:
+                    if zi.external_attr == 0: 
+                        zi = ZeroedZipInfo(zi)
+
                     if path == "mimetype":
                         outf.writestr(zi, inf.read('mimetype'))
                     elif path == "META-INF/encryption.xml":
index 176c77fd377bc50bcba982c7eab8d177ba32af3c..67199351a83703d13313f932f78a765390768fd5 100644 (file)
@@ -16,6 +16,7 @@ Removes various watermarks from EPUB files
 
 import traceback
 from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
+from zeroedzipinfo import ZeroedZipInfo
 from contextlib import closing
 from lxml import etree
 import re
@@ -133,13 +134,22 @@ def removeHTMLwatermarks(object, path_to_ebook):
                         zi.extra = oldzi.extra
                         zi.internal_attr = oldzi.internal_attr
                         zi.external_attr = oldzi.external_attr
+                        zi.volume = oldzi.volume
                         zi.create_system = oldzi.create_system
+                        zi.create_version = oldzi.create_version
+
                         if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
                             # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
                             zi.flag_bits |= 0x800
                     except:
                         pass
 
+                    # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                    # if it's NULL, so we need a workaround:
+                    if zi.external_attr == 0: 
+                        zi = ZeroedZipInfo(zi)
+
+
                     outf.writestr(zi, data)
         except:
             traceback.print_exc()
@@ -249,13 +259,21 @@ def removeOPFwatermarks(object, path_to_ebook):
                         zi.extra = oldzi.extra
                         zi.internal_attr = oldzi.internal_attr
                         zi.external_attr = oldzi.external_attr
+                        zi.volume = oldzi.volume
                         zi.create_system = oldzi.create_system
+                        zi.create_version = oldzi.create_version
+
                         if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
                             # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
                             zi.flag_bits |= 0x800
                     except:
                         pass
 
+                    # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                    # if it's NULL, so we need a workaround:
+                    if zi.external_attr == 0: 
+                        zi = ZeroedZipInfo(zi)
+
                     outf.writestr(zi, data)
         except:
             traceback.print_exc()
@@ -301,13 +319,21 @@ def removeCDPwatermark(object, path_to_ebook):
                     zi.extra = oldzi.extra
                     zi.internal_attr = oldzi.internal_attr
                     zi.external_attr = oldzi.external_attr
+                    zi.volume = oldzi.volume
                     zi.create_system = oldzi.create_system
+                    zi.create_version = oldzi.create_version
+
                     if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
                         # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
                         zi.flag_bits |= 0x800
                 except:
                     pass
 
+                # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                # if it's NULL, so we need a workaround:
+                if zi.external_attr == 0: 
+                    zi = ZeroedZipInfo(zi)
+
                 outf.writestr(zi, data)
         
         print("Watermark: Successfully removed cdp.info watermark")
index 6b4b67608b1804d7b89671701e3ac7a909ccb42f..094cb8c89231ab956706de5812c87da2e0d5b1a1 100644 (file)
@@ -48,6 +48,7 @@ import base64
 import zlib
 import zipfile
 from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
+from zeroedzipinfo import ZeroedZipInfo
 from contextlib import closing
 from lxml import etree
 from uuid import UUID
@@ -356,12 +357,23 @@ def decryptBook(userkey, inpath, outpath):
                         zi.internal_attr = oldzi.internal_attr
                         # external attributes are dependent on the create system, so copy both.
                         zi.external_attr = oldzi.external_attr
+
+                        zi.volume = oldzi.volume
                         zi.create_system = oldzi.create_system
+                        zi.create_version = oldzi.create_version
+
                         if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
                             # If the file name or the comment contains any non-ASCII char, set the UTF8-flag
                             zi.flag_bits |= 0x800
                     except:
                         pass
+
+                    # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                    # if it's NULL, so we need a workaround:
+                    if zi.external_attr == 0: 
+                        zi = ZeroedZipInfo(zi)
+
+
                     if path == "META-INF/encryption.xml":
                         outf.writestr(zi, data)
                     else:
diff --git a/DeDRM_plugin/zeroedzipinfo.py b/DeDRM_plugin/zeroedzipinfo.py
new file mode 100644 (file)
index 0000000..08c65d0
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+
+"""
+Python 3's "zipfile" has an annoying bug where the `external_attr` field 
+of a ZIP file cannot be set to 0. However, if the original DRMed ZIP has 
+that set to 0 then we want the DRM-free ZIP to have that as 0, too. 
+See https://github.com/python/cpython/issues/87713
+
+We cannot just set the "external_attr" to 0 as the code to save the ZIP
+resets that variable. 
+
+So, here's a class that inherits from ZipInfo and ensures that EVERY 
+read access to that variable will return a 0 ...
+
+"""
+
+import zipfile
+
+class ZeroedZipInfo(zipfile.ZipInfo):
+    def __init__(self, zinfo):
+        for k in self.__slots__:
+            if hasattr(zinfo, k):
+                setattr(self, k, getattr(zinfo, k))
+
+    def __getattribute__(self, name):
+        if name == "external_attr":
+            return 0
+        return object.__getattribute__(self, name)
index aef9ea3f50eee967ef32bf0c1bf900496cffdca1..1941cc06503399c9d1810eba4a42adc3a1ca4aff 100755 (executable)
@@ -394,6 +394,19 @@ class ZipInfo (object):
             extra = extra[ln+4:]
 
 
+class ZeroedZipInfo(ZipInfo):
+    def __init__(self, zinfo):
+        for k in self.__slots__:
+            if hasattr(zinfo, k):
+                setattr(self, k, getattr(zinfo, k))
+
+    def __getattribute__(self, name):
+        if name == "external_attr":
+            return 0
+        return object.__getattribute__(self, name)
+
+
+
 class _ZipDecrypter:
     """Class to handle decryption of files stored within a ZIP archive.
 
index 3fbfbcea4058ab75c02ba6ae51213b7067d9f295..9cb4ff1557555cc01f71648c16ba8bebdb52da04 100644 (file)
@@ -26,6 +26,7 @@ import sys, os
 
 import zlib
 import zipfilerugged
+from zipfilerugged import ZipInfo, ZeroedZipInfo
 import getopt
 from struct import unpack
 
@@ -36,12 +37,6 @@ _FILENAME_OFFSET = 30
 _MAX_SIZE = 64 * 1024
 _MIMETYPE = 'application/epub+zip'
 
-class ZipInfo(zipfilerugged.ZipInfo):
-    def __init__(self, *args, **kwargs):
-        if 'compress_type' in kwargs:
-            compress_type = kwargs.pop('compress_type')
-        super(ZipInfo, self).__init__(*args, **kwargs)
-        self.compress_type = compress_type
 
 class fixZip:
     def __init__(self, zinput, zoutput):
@@ -117,7 +112,8 @@ class fixZip:
         # if epub write mimetype file first, with no compression
         if self.ztype == 'epub':
             # first get a ZipInfo with current time and no compression
-            mimeinfo = ZipInfo(b'mimetype',compress_type=zipfilerugged.ZIP_STORED)
+            mimeinfo = ZipInfo(b'mimetype')
+            mimeinfo.compress_type = zipfilerugged.ZIP_STORED
             mimeinfo.internal_attr = 1 # text file
             try:
                 # if the mimetype is present, get its info, including time-stamp
@@ -129,8 +125,16 @@ class fixZip:
                 mimeinfo.internal_attr = oldmimeinfo.internal_attr
                 mimeinfo.external_attr = oldmimeinfo.external_attr
                 mimeinfo.create_system = oldmimeinfo.create_system
+                mimeinfo.create_version = oldmimeinfo.create_version
+                mimeinfo.volume = oldmimeinfo.volume
             except:
                 pass
+
+            # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+            # if it's NULL, so we need a workaround:
+            if mimeinfo.external_attr == 0: 
+                mimeinfo = ZeroedZipInfo(mimeinfo)
+
             self.outzip.writestr(mimeinfo, _MIMETYPE.encode('ascii'))
 
         # write the rest of the files
@@ -145,13 +149,23 @@ class fixZip:
                     zinfo.filename = local_name
 
                 # create new ZipInfo with only the useful attributes from the old info
-                nzinfo = ZipInfo(zinfo.filename, zinfo.date_time, compress_type=zinfo.compress_type)
+                nzinfo = ZipInfo(zinfo.filename)
+                nzinfo.date_time = zinfo.date_time
+                nzinfo.compress_type = zinfo.compress_type
                 nzinfo.comment=zinfo.comment
                 nzinfo.extra=zinfo.extra
                 nzinfo.internal_attr=zinfo.internal_attr
                 nzinfo.external_attr=zinfo.external_attr
                 nzinfo.create_system=zinfo.create_system
+                nzinfo.create_version = zinfo.create_version
+                nzinfo.volume = zinfo.volume
                 nzinfo.flag_bits = zinfo.flag_bits & 0x800  # preserve UTF-8 flag
+
+                # Python 3 has a bug where the external_attr is reset to `0o600 << 16`
+                # if it's NULL, so we need a workaround:
+                if nzinfo.external_attr == 0: 
+                    nzinfo = ZeroedZipInfo(nzinfo)
+
                 self.outzip.writestr(nzinfo,data)
 
         self.bzf.close()