From 768d49094c248a460fd5f740343aa10b64e5cd5a Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Mon, 22 Jun 2026 18:21:42 -0500 Subject: [PATCH 01/14] Fix path traversal in Topaz extraction (arbitrary file write) The Topaz header record "tag" is read verbatim from the untrusted book file by bookReadString() and then used unsanitized to build the output filename in extractFiles(). A crafted tag such as "../../foo" let a malicious .azw/Topaz file write attacker-controlled bytes outside the extraction directory. The payload content requires no book key, since an unencrypted record with compressedLength == 0 is returned raw. Strip the record name to its basename before joining it to destdir so traversal sequences (../, /, \) can no longer escape, and add an abspath-based containment check as defense in depth. Co-Authored-By: Claude Opus 4.8 --- DeDRM_plugin/topazextract.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DeDRM_plugin/topazextract.py b/DeDRM_plugin/topazextract.py index 97d75a7..4f2c057 100644 --- a/DeDRM_plugin/topazextract.py +++ b/DeDRM_plugin/topazextract.py @@ -358,7 +358,16 @@ class TopazBook: destdir = os.path.join(outdir,"page") if name == b'glyphs': destdir = os.path.join(outdir,"glyphs") + # The record name is read verbatim from the (untrusted) book + # file, so strip any directory components to stop a crafted + # name (e.g. "../../foo") from writing outside of destdir. + fname = os.path.basename(fname) outputFile = os.path.join(destdir,fname) + # Defense in depth: never let the resolved path escape destdir. + real_out = os.path.abspath(outputFile) + real_dir = os.path.abspath(destdir) + if not (real_out == real_dir or real_out.startswith(real_dir + os.sep)): + raise DrmException("Invalid book record name: {0}".format(repr(name))) print(".", end=' ') record = self.getBookPayloadRecord(name,index) if isinstance(record, str): -- 2.47.3 From 815c621f01e45608b22c13508d2b7077b687e776 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Mon, 22 Jun 2026 18:27:43 -0500 Subject: [PATCH 02/14] Manage dependencies with Poetry; drop abandoned pycrypto Add a Poetry manifest (pyproject.toml) and lock file to manage the development / standalone-CLI environment for the plugins. The plugins themselves run inside calibre's bundled Python, so the project is set to package-mode = false and the manifest documents the real third-party dependency set rather than building a distributable package. Declared dependencies: - pycryptodomex (>=3.20): maintained crypto library exposing the `Cryptodome` namespace that every crypto import already prefers. This replaces the abandoned pycrypto (unmaintained since 2014, CVE-2013-7459), which is no longer needed and is not declared. - lxml (>=5.0): EPUB/PDF/ADEPT XML handling. - apsw (>=3.46): optional `nook` group, only used by ignoblekeyWindowsStore.py for Nook Windows Store key extraction. calibre/calibre_lzma/PyQt are supplied by the calibre runtime and the Python <3.3 lzma fallbacks (backports.lzma, pylzma) are unnecessary on the supported Python 3.8+ range, so none are declared. Also update the stale PyCrypto install instructions in ignoblekeyGenPassHash.py to point at the maintained pycryptodomex. Co-Authored-By: Claude Opus 4.8 --- DeDRM_plugin/ignoblekeyGenPassHash.py | 10 +- poetry.lock | 278 ++++++++++++++++++++++++++ pyproject.toml | 40 ++++ 3 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/DeDRM_plugin/ignoblekeyGenPassHash.py b/DeDRM_plugin/ignoblekeyGenPassHash.py index f050bd9..e856cdb 100644 --- a/DeDRM_plugin/ignoblekeyGenPassHash.py +++ b/DeDRM_plugin/ignoblekeyGenPassHash.py @@ -7,12 +7,10 @@ # Released under the terms of the GNU General Public Licence, version 3 # -# Windows users: Before running this program, you must first install Python. -# We recommend ActiveState Python 2.7.X for Windows (x86) from -# http://www.activestate.com/activepython/downloads. -# You must also install PyCrypto from -# http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make certain to install the version for Python 2.7). +# Windows users: Before running this program, you must first install Python 3. +# You must also install the maintained pycryptodomex package +# (https://pypi.org/project/pycryptodomex/) via "pip install pycryptodomex". +# The old PyCrypto library is abandoned and is no longer required. # Then save this script file as ignoblekeygen.pyw and double-click on it to run it. # # Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..5c30295 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,278 @@ +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. + +[[package]] +name = "apsw" +version = "3.46.1.0" +description = "Another Python SQLite Wrapper" +optional = false +python-versions = ">=3.8" +groups = ["nook"] +files = [ + {file = "apsw-3.46.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ebfaf4d84bc61ecf79587acf31a31f732be72cb8fc8c999ce9453147a2e57dc"}, + {file = "apsw-3.46.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2545b9c885f98d7dedf9c0f9c485f84d39c5a40352b8a3c3f05f99e1e1d2ae73"}, + {file = "apsw-3.46.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5b83967c1b68877976a06bb8a2eb314b1f8923d787f6d7cd41b48776847bfe6"}, + {file = "apsw-3.46.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110dae383750f75fc57d63ea14a759e4309886e1dd5243993db7eaa8f9d06d90"}, + {file = "apsw-3.46.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e403aab2b5653152e85341218a49d73eed66319f0deccce5db21f7a89def394a"}, + {file = "apsw-3.46.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3382459357f0819b631087b32818a502da47c84ddea76132d76ec02aa473af45"}, + {file = "apsw-3.46.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d43d80bbd497ddb86fa5774bd80df1825253a3765e01e707cbcc6242f06f21eb"}, + {file = "apsw-3.46.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:402233ed77b7bd99f81c5b68bd8201dd1c4c1dedc40d12871f709b922842d5c6"}, + {file = "apsw-3.46.1.0-cp310-cp310-win32.whl", hash = "sha256:c17dab34c487ee562f81672f6bf3c067493e017d1a8c816485e6de06ce25f620"}, + {file = "apsw-3.46.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9484b47e792ad54b36fced07b9ff91b9ab32f0d7de44da3a9b5fa9d78e28ddae"}, + {file = "apsw-3.46.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8acacf3a0187cbd4c82c9eaa2c7e2704c13997a351efdffbdfa69ea1778bda1f"}, + {file = "apsw-3.46.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2fcc5fd76a4a21fb1cc1694495e221206a46d932c675ba5dda8f070262347e0b"}, + {file = "apsw-3.46.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca319360995762fbed9e7d252c3ae4ea84af2d7bc1aef21adbcb4e088a416373"}, + {file = "apsw-3.46.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42036093f8b7dd66bbb18b717ef6dfa62c4a2acd9bcdf05fdf5caedf77a47c01"}, + {file = "apsw-3.46.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d54efc7d7d58b782dc84f29c5d25d190984dec20d8233484d3b092ded1241e1"}, + {file = "apsw-3.46.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3864151c23edd3a232e059925bd37044e2c1b90f20ba1d0b46005d0e5d97d10d"}, + {file = "apsw-3.46.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3e42ebb5997fcd6234cdb888f641ca37a7e9d03c13c4f2e1e0ac66151a182e79"}, + {file = "apsw-3.46.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7ea887719b60d48ac569eb42594f7dd772ab2b2287aaae9c9a007789467aa26a"}, + {file = "apsw-3.46.1.0-cp311-cp311-win32.whl", hash = "sha256:67754bc4c0b2dda1a112f0adfdd6d25c2f724d8c086decb9690349056f799eed"}, + {file = "apsw-3.46.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fef6891bc388d92ac8208db2e6f5cd90bd75a64930147f515f2e28526a278c81"}, + {file = "apsw-3.46.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a892c3dccbb2f96611a0707fb4cf1573c88a4a2898b45f013a202557f602faa8"}, + {file = "apsw-3.46.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e01e2cf7faae0d86540e9c638778c98cdd14b365136d9b11d2a0a7dfe4893f3"}, + {file = "apsw-3.46.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf96999557f5ccc5c8424b0c8637447652994e26b4fe87b72626def24c53dcf1"}, + {file = "apsw-3.46.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925c01733c0de8adf7f3cbf299e4bcb38961b62de4436f8704e5ff9800523a7c"}, + {file = "apsw-3.46.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:979f1fca9ec09c1e8bd466ad17fde25dffb06ea621bd8b7c08e404d042c4d362"}, + {file = "apsw-3.46.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e02c614ca3afceb6c80349485dd2d49fea584a885794ca732513d8aa746781d7"}, + {file = "apsw-3.46.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c9c130253eba2f70547253f54db05d0e6aa41962d9d712b9e1b8efba1ea2990b"}, + {file = "apsw-3.46.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:999f8fc2607a98ecfddb9d266bd746ae9a08dfa67841700ccfb0108a9cfe9104"}, + {file = "apsw-3.46.1.0-cp312-cp312-win32.whl", hash = "sha256:0f864580269c5cfe7aed899057a6e76f06940d2bc4134c2e32d36aadad0b3e29"}, + {file = "apsw-3.46.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb770bbe185d3cd6609bb245e22108490c81a025e3be82cd278116b5e21cbbee"}, + {file = "apsw-3.46.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8113139847f1ad7023a2b8ab8cee15ceec2406c9fbc6709c64a029947485bc23"}, + {file = "apsw-3.46.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:038e9550dc24413a12fde36492ab275410c35982e97d33cc5fd7c18325bbc9e1"}, + {file = "apsw-3.46.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1779f6d512d07b1fa914d268c42ac47170fd41aeae3352c3ce1c5c9f410496b9"}, + {file = "apsw-3.46.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2497696ccb5ac928ba3bbae988a82e00d0be3242b7bdd0553ce4447bdcc73b"}, + {file = "apsw-3.46.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b182bafe729822f7a934d081d9190b76e0dae1f5f098efa7430fcdd38e3a315"}, + {file = "apsw-3.46.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d08cf0df8f58b4bdf8c0b7a4c8e70f2a8840be631f40dae848b384636a22fe0d"}, + {file = "apsw-3.46.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d37e2f6e1a22bb1a82905237b4d433124ccc53a62f918b9cf7c42c6e49241d0"}, + {file = "apsw-3.46.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d49e1996c13661011505cd53502e580c63062eafeebaebcd80ec34b435475c4"}, + {file = "apsw-3.46.1.0-cp313-cp313-win32.whl", hash = "sha256:42039f5d650ef1bb12e1304aebe47338c20bead38b41bae0f22f98e78cc6855c"}, + {file = "apsw-3.46.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c37abd9f344aefa11c3fe213738c1db4fe49bacef1aee360087fa1ac840b1c4"}, + {file = "apsw-3.46.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a8cd20bff7e1294d8b779153f2ff0fbd7821c4b479b31caef4b1427f3b443307"}, + {file = "apsw-3.46.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9b7e6395b8b20681a9b4a5f9621c9b1800e32a332cf75010bc8fde1d43465610"}, + {file = "apsw-3.46.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:348c88254bf05527909494a690d846cae5a95dd87e2359788b4ccb4d4a56c64a"}, + {file = "apsw-3.46.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d508bcbf81d0f8339572e8e9775cc45991d2548f19605a5445d8dc05f23910f4"}, + {file = "apsw-3.46.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7bf2ca987fcb136bdb23c241e0b18469a2c45255bee80d7884ff56bc6d5461"}, + {file = "apsw-3.46.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:81f7c3ca9267a290922ddef73293c86bc373afc128867412d6f844a4a271a381"}, + {file = "apsw-3.46.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:63278cee066c2365327d5d5976570834a559b9f64068186038af29b08063ba5a"}, + {file = "apsw-3.46.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f53f489ebb024464e9223de9d8a139a2e206d33de1ab0538a01962dfe40c6526"}, + {file = "apsw-3.46.1.0-cp38-cp38-win32.whl", hash = "sha256:fc60738ad5594f72eb611178f161869dc7ff02789468c963296c4da743415e55"}, + {file = "apsw-3.46.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f48ef4f7d061b819cc35878ac661835e84debe17886abbefa7b0f935a24b85"}, + {file = "apsw-3.46.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:85552b0696342e601420e02beba02ef8b1544eeb92895d771d795c1e5e696285"}, + {file = "apsw-3.46.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b0b71f1f76200907782c6bd17b6b623b4ccc95f6d4187f0361c8f5c1a3e9fd81"}, + {file = "apsw-3.46.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755f0cd2a3b70436e69b347ffb0774cca32db96ca7cf9b8660f29226d8eec2b1"}, + {file = "apsw-3.46.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c95adc014380489b37c97a70ab6ead31a1774d35f921e309407241a035d1485"}, + {file = "apsw-3.46.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:029f03385379882a6ce5df9713ebed86dde1cdf0435181431c264d1333719d13"}, + {file = "apsw-3.46.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fed308f151dab9ca3052ee9c35beedfb5fd788734b24514e6bedf58f6f86f5d"}, + {file = "apsw-3.46.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:66773bc9b0428a4178bb5dd0dbbca00104e184252832ec1bcd81712a29ef65ed"}, + {file = "apsw-3.46.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:55d248a9d55d39d366f532f4ae70bdf7f2b8269f5e75b71742e1bdf039853151"}, + {file = "apsw-3.46.1.0-cp39-cp39-win32.whl", hash = "sha256:4739482e35f5da3cc694959c43c813433b444ce865b60d5d78ba0734eb4a3840"}, + {file = "apsw-3.46.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:2bffff5cbfcd84ce7409d3ecb885b4707a264effbaa950f0ba5c68eaaca1ad5d"}, + {file = "apsw-3.46.1.0.tar.gz", hash = "sha256:96e3dfad1fd0cc77a778aa6b27468292041a8e9cb1f2dcf06bd773762c9b0c0c"}, +] + +[[package]] +name = "lxml" +version = "6.1.1" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60"}, + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1"}, + {file = "lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a"}, + {file = "lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5"}, + {file = "lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08"}, + {file = "lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621"}, + {file = "lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28"}, + {file = "lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a"}, + {file = "lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a"}, + {file = "lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77"}, + {file = "lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947"}, + {file = "lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca"}, + {file = "lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660"}, + {file = "lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb"}, + {file = "lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603"}, + {file = "lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137"}, + {file = "lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e"}, + {file = "lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1"}, + {file = "lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e"}, + {file = "lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c"}, + {file = "lxml-6.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4"}, + {file = "lxml-6.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e"}, + {file = "lxml-6.1.1-cp38-cp38-win32.whl", hash = "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3"}, + {file = "lxml-6.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d"}, + {file = "lxml-6.1.1-cp39-cp39-win32.whl", hash = "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186"}, + {file = "lxml-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730"}, + {file = "lxml-6.1.1-cp39-cp39-win_arm64.whl", hash = "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84"}, + {file = "lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + +[[package]] +name = "pycryptodomex" +version = "3.23.0" +description = "Cryptographic library for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +files = [ + {file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"}, + {file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"}, +] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.8,<4.0" +content-hash = "9837c213244109f02619c8a2146d5ab4904fd272fb3e57aa897dca0f19633add" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f8c4a94 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +# Poetry-managed development environment for DeDRM_tools. +# +# Note: the DeDRM and Obok plugins are distributed as calibre plugin zips and +# run inside calibre's bundled Python interpreter -- they are NOT pip-installed. +# This manifest therefore manages the *development* / standalone-CLI environment +# (running the scripts outside calibre, testing, etc.) and documents the real +# third-party dependency set, rather than producing a distributable package. +# Hence `package-mode = false`. +# +# Dependencies provided by the calibre runtime (do not pip-install them here): +# calibre, calibre_plugins, calibre_lzma, PyQt5 / PyQt4 +# +# Crypto: the code imports the `Cryptodome` namespace first (pycryptodomex, +# actively maintained) and only falls back to the `Crypto` namespace if it is +# missing. The legacy `Crypto` namespace is satisfied by the maintained +# pycryptodome package as well, so the abandoned `pycrypto` package (unmaintained +# since 2014, CVE-2013-7459) is not required and is intentionally not declared. + +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.8,<4.0" + +# Actively-maintained crypto library, exposes the `Cryptodome` namespace that +# all crypto imports prefer. Replaces the abandoned `pycrypto`. +pycryptodomex = ">=3.20" + +# XML handling for Adobe ADEPT / EPUB / PDF DRM (ineptepub, ineptpdf, +# epubfontdecrypt, epubwatermark, ignoblekey*). +lxml = ">=5.0" + +# SQLite access for Nook (Barnes & Noble) Windows Store key extraction is only +# needed by DeDRM_plugin/ignoblekeyWindowsStore.py. Kept in an optional group so +# the base environment installs cleanly on platforms where it is not required. +[tool.poetry.group.nook] +optional = true + +[tool.poetry.group.nook.dependencies] +apsw = ">=3.46" -- 2.47.3 From 838700fbec8b6d77a4ece69eef12d717f416916e Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Mon, 22 Jun 2026 18:42:33 -0500 Subject: [PATCH 03/14] Remove Python 2 compatibility shims The plugins target calibre 5/6 (Python 3) and the Poetry environment pins Python >=3.8, so the Python 2 fallbacks are dead code. This removes them throughout both plugins (behaviour on Python 3 is unchanged): - Drop all `from __future__` imports. - Collapse `if sys.version_info[0] == 2: ... else: ...` blocks to their Python 3 branch (ineptpdf, mobidedrm, kindlekey, kgenpids, alfcrypto, erdr2pml, ineptepub, obok, and the various unpad() helpers, etc.). - Replace `_winreg` import fallbacks with plain `import winreg`, and delete the py2-only adobekey_winreg_unicode module (now unreferenced). - Drop py2 name shims: `unicode`/`unichr`, `.iteritems()`, `from StringIO import StringIO`, `htmlentitydefs` fallback, and the Windows CommandLineToArgvW dance in unicode_argv (py3 sys.argv is already Unicode on every platform). - Remove the "Calibre < 5" (py2) bugfix block from the compat header. Verified: every .py file in both plugins still byte-compiles. Scope: the maintained DeDRM_plugin and Obok_plugin only. The archival standalone scripts under Other_Tools/ are left as historical snapshots. Co-Authored-By: Claude Opus 4.8 --- DeDRM_plugin/__calibre_compat_code.py | 6 - DeDRM_plugin/__init__.py | 2 - DeDRM_plugin/adobekey.py | 24 +-- DeDRM_plugin/adobekey_get_passhash.py | 10 +- DeDRM_plugin/adobekey_winreg_unicode.py | 271 ------------------------ DeDRM_plugin/alfcrypto.py | 18 +- DeDRM_plugin/androidkindlekey.py | 5 +- DeDRM_plugin/argv_utils.py | 49 +---- DeDRM_plugin/config.py | 8 +- DeDRM_plugin/epubfontdecrypt.py | 2 - DeDRM_plugin/erdr2pml.py | 8 +- DeDRM_plugin/genbook.py | 1 - DeDRM_plugin/ignoblekeyAndroid.py | 5 +- DeDRM_plugin/ignoblekeyNookStudy.py | 22 +- DeDRM_plugin/ignoblekeyWindowsStore.py | 5 +- DeDRM_plugin/ineptepub.py | 5 +- DeDRM_plugin/ineptpdf.py | 82 ++----- DeDRM_plugin/k4mobidedrm.py | 5 +- DeDRM_plugin/kgenpids.py | 22 +- DeDRM_plugin/kindlekey.py | 27 +-- DeDRM_plugin/kindlepid.py | 5 +- DeDRM_plugin/mobidedrm.py | 23 +- DeDRM_plugin/standalone/__init__.py | 2 - DeDRM_plugin/standalone/jsonconfig.py | 2 - DeDRM_plugin/standalone/passhash.py | 2 - DeDRM_plugin/standalone/remove_drm.py | 2 - DeDRM_plugin/topazextract.py | 1 - DeDRM_plugin/utilities.py | 8 +- Obok_plugin/__init__.py | 3 - Obok_plugin/common_utils.py | 6 +- Obok_plugin/dialogs.py | 3 - Obok_plugin/obok/legacy_obok.py | 5 +- Obok_plugin/obok/obok.py | 26 +-- Obok_plugin/utilities.py | 5 +- 34 files changed, 79 insertions(+), 591 deletions(-) delete mode 100644 DeDRM_plugin/adobekey_winreg_unicode.py diff --git a/DeDRM_plugin/__calibre_compat_code.py b/DeDRM_plugin/__calibre_compat_code.py index b44b8f3..8257c3e 100644 --- a/DeDRM_plugin/__calibre_compat_code.py +++ b/DeDRM_plugin/__calibre_compat_code.py @@ -8,12 +8,6 @@ if os.path.dirname(os.path.dirname(os.path.abspath(__file__))) not in sys.path: if os.path.dirname(os.path.abspath(__file__)) not in sys.path: sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -# Bugfix for Calibre < 5: -if "calibre" in sys.modules and sys.version_info[0] == 2: - from calibre.utils.config import config_dir - if os.path.join(config_dir, "plugins", "DeDRM.zip") not in sys.path: - sys.path.insert(0, os.path.join(config_dir, "plugins", "DeDRM.zip")) - if "calibre" in sys.modules: # Explicitly set the package identifier so we are allowed to import stuff ... __package__ = "calibre_plugins.dedrm" diff --git a/DeDRM_plugin/__init__.py b/DeDRM_plugin/__init__.py index 8200122..29e585a 100644 --- a/DeDRM_plugin/__init__.py +++ b/DeDRM_plugin/__init__.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from __future__ import print_function - # __init__.py for DeDRM_plugin # Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2021-2023 NoDRM diff --git a/DeDRM_plugin/adobekey.py b/DeDRM_plugin/adobekey.py index 5ad771f..b212db9 100644 --- a/DeDRM_plugin/adobekey.py +++ b/DeDRM_plugin/adobekey.py @@ -68,10 +68,7 @@ if iswindows: c_long, c_ulong from ctypes.wintypes import LPVOID, DWORD, BOOL - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg try: from Cryptodome.Cipher import AES @@ -79,10 +76,7 @@ if iswindows: from Crypto.Cipher import AES def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] @@ -135,19 +129,9 @@ if iswindows: GetUserName = GetUserName() def GetUserName2(): - try: - from winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER - except ImportError: - # We're on Python 2 - try: - # The default _winreg on Python2 isn't unicode-safe. - # Check if we have winreg_unicode, a unicode-safe alternative. - # Without winreg_unicode, this will fail with Unicode chars in the username. - from adobekey_winreg_unicode import OpenKey, QueryValueEx, HKEY_CURRENT_USER - except: - from _winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER + from winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER - try: + try: DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device' regkey = OpenKey(HKEY_CURRENT_USER, DEVICE_KEY_PATH) userREG = QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2] diff --git a/DeDRM_plugin/adobekey_get_passhash.py b/DeDRM_plugin/adobekey_get_passhash.py index 1e9b8e2..d894c6b 100644 --- a/DeDRM_plugin/adobekey_get_passhash.py +++ b/DeDRM_plugin/adobekey_get_passhash.py @@ -28,10 +28,7 @@ except ImportError: def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] @@ -60,10 +57,7 @@ def decrypt_passhash(passhash, fp): if iswindows: - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation' diff --git a/DeDRM_plugin/adobekey_winreg_unicode.py b/DeDRM_plugin/adobekey_winreg_unicode.py deleted file mode 100644 index 6c719c4..0000000 --- a/DeDRM_plugin/adobekey_winreg_unicode.py +++ /dev/null @@ -1,271 +0,0 @@ -# This is based on https://github.com/DanielStutzbach/winreg_unicode -# The original _winreg in Python2 doesn't support unicode. -# This causes issues if there's unicode chars in the username needed to decrypt the key. - -''' -Copyright 2010 Stutzbach Enterprises, LLC (daniel@stutzbachenterprises.com) - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - 3. The name of the author may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -''' - -import ctypes, ctypes.wintypes - -ERROR_SUCCESS = 0 -ERROR_MORE_DATA = 234 - -KEY_READ = 0x20019 - -REG_NONE = 0 -REG_SZ = 1 -REG_EXPAND_SZ = 2 -REG_BINARY = 3 -REG_DWORD = 4 -REG_DWORD_BIG_ENDIAN = 5 -REG_DWORD_LITTLE_ENDIAN = 4 -REG_LINK = 6 -REG_MULTI_SZ = 7 -REG_RESOURCE_LIST = 8 -REG_FULL_RESOURCE_DESCRIPTOR = 9 -REG_RESOURCE_REQUIREMENTS_LIST = 10 - -c_HKEY = ctypes.c_void_p -DWORD = ctypes.wintypes.DWORD -BYTE = ctypes.wintypes.BYTE -LPDWORD = ctypes.POINTER(DWORD) -LPBYTE = ctypes.POINTER(BYTE) - -advapi32 = ctypes.windll.advapi32 - -class FILETIME(ctypes.Structure): - _fields_ = [("dwLowDateTime", DWORD), - ("dwHighDateTime", DWORD)] - -RegCloseKey = advapi32.RegCloseKey -RegCloseKey.restype = ctypes.c_long -RegCloseKey.argtypes = [c_HKEY] - -RegOpenKeyEx = advapi32.RegOpenKeyExW -RegOpenKeyEx.restype = ctypes.c_long -RegOpenKeyEx.argtypes = [c_HKEY, ctypes.c_wchar_p, ctypes.c_ulong, - ctypes.c_ulong, ctypes.POINTER(c_HKEY)] - -RegQueryInfoKey = advapi32.RegQueryInfoKeyW -RegQueryInfoKey.restype = ctypes.c_long -RegQueryInfoKey.argtypes = [c_HKEY, ctypes.c_wchar_p, LPDWORD, LPDWORD, - LPDWORD, LPDWORD, LPDWORD, LPDWORD, - LPDWORD, LPDWORD, LPDWORD, - ctypes.POINTER(FILETIME)] - -RegEnumValue = advapi32.RegEnumValueW -RegEnumValue.restype = ctypes.c_long -RegEnumValue.argtypes = [c_HKEY, DWORD, ctypes.c_wchar_p, LPDWORD, - LPDWORD, LPDWORD, LPBYTE, LPDWORD] - -RegEnumKeyEx = advapi32.RegEnumKeyExW -RegEnumKeyEx.restype = ctypes.c_long -RegEnumKeyEx.argtypes = [c_HKEY, DWORD, ctypes.c_wchar_p, LPDWORD, - LPDWORD, ctypes.c_wchar_p, LPDWORD, - ctypes.POINTER(FILETIME)] - -RegQueryValueEx = advapi32.RegQueryValueExW -RegQueryValueEx.restype = ctypes.c_long -RegQueryValueEx.argtypes = [c_HKEY, ctypes.c_wchar_p, LPDWORD, LPDWORD, - LPBYTE, LPDWORD] - -def check_code(code): - if code == ERROR_SUCCESS: - return - raise ctypes.WinError(2) - -class HKEY(object): - def __init__(self): - self.hkey = c_HKEY() - - def __enter__(self): - return self - - def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): - self.Close() - return False - - def Detach(self): - rv = self.cast(self.hkey, self.c_ulong).value - self.hkey = c_HKEY() - return rv - - def __nonzero__(self): - return bool(self.hkey) - - def Close(self): - if not self.hkey: - return - if RegCloseKey is None or check_code is None or c_HKEY is None: - return # globals become None during exit - rc = RegCloseKey(self.hkey) - self.hkey = c_HKEY() - check_code(rc) - - def __del__(self): - self.Close() - -class RootHKEY(ctypes.Structure): - def __init__(self, value): - self.hkey = c_HKEY(value) - - def Close(self): - pass - -HKEY_CLASSES_ROOT = RootHKEY(0x80000000) -HKEY_CURRENT_USER = RootHKEY(0x80000001) -HKEY_LOCAL_MACHINE = RootHKEY(0x80000002) -HKEY_USERS = RootHKEY(0x80000003) -HKEY_PERFORMANCE_DATA = RootHKEY(0x80000004) -HKEY_CURRENT_CONFIG = RootHKEY(0x80000005) -HKEY_DYN_DATA = RootHKEY(0x80000006) - -def OpenKey(key, sub_key): - new_key = HKEY() - rc = RegOpenKeyEx(key.hkey, sub_key, 0, KEY_READ, - ctypes.cast(ctypes.byref(new_key.hkey), - ctypes.POINTER(c_HKEY))) - check_code(rc) - return new_key - -def QueryInfoKey(key): - null = LPDWORD() - num_sub_keys = DWORD() - num_values = DWORD() - ft = FILETIME() - rc = RegQueryInfoKey(key.hkey, ctypes.c_wchar_p(), null, null, - ctypes.byref(num_sub_keys), null, null, - ctypes.byref(num_values), null, null, null, - ctypes.byref(ft)) - check_code(rc) - return (num_sub_keys.value, num_values.value, - ft.dwLowDateTime | (ft.dwHighDateTime << 32)) - -def EnumValue(key, index): - null = LPDWORD() - value_size = DWORD() - data_size = DWORD() - rc = RegQueryInfoKey(key.hkey, ctypes.c_wchar_p(), null, null, null, - null, null, null, - ctypes.byref(value_size), ctypes.byref(data_size), - null, ctypes.POINTER(FILETIME)()) - check_code(rc) - value_size.value += 1 - data_size.value += 1 - - value = ctypes.create_unicode_buffer(value_size.value) - - while True: - data = ctypes.create_string_buffer(data_size.value) - - tmp_value_size = DWORD(value_size.value) - tmp_data_size = DWORD(data_size.value) - typ = DWORD() - rc = RegEnumValue(key.hkey, index, - ctypes.cast(value, ctypes.c_wchar_p), - ctypes.byref(tmp_value_size), null, - ctypes.byref(typ), - ctypes.cast(data, LPBYTE), - ctypes.byref(tmp_data_size)) - - if rc != ERROR_MORE_DATA: - break - - data_size.value *= 2 - - check_code(rc) - return (value.value, Reg2Py(data, tmp_data_size.value, typ.value), - typ.value) - -def split_multi_sz(data, size): - if size == 0: - return [] - Q = size - P = 0 - rv = [] - while P < Q and data[P].value != u'\0': - rv.append[P] - while P < Q and data[P].value != u'\0': - P += 1 - P += 1 - rv.append(size) - return [ctypes.wstring_at(ctypes.pointer(data[rv[i]]), - rv[i+1] - rv[i]).rstrip(u'\x00') - for i in range(len(rv)-1)] - -def Reg2Py(data, size, typ): - if typ == REG_DWORD: - if size == 0: - return 0 - return ctypes.cast(data, ctypes.POINTER(ctypes.c_int)).contents.value - elif typ == REG_SZ or typ == REG_EXPAND_SZ: - return ctypes.wstring_at(data, size // 2).rstrip(u'\x00') - elif typ == REG_MULTI_SZ: - return split_multi_sz(ctypes.cast(data, ctypes.c_wchar_p), size // 2) - else: - if size == 0: - return None - return ctypes.string_at(data, size) - -def EnumKey(key, index): - tmpbuf = ctypes.create_unicode_buffer(257) - length = DWORD(257) - rc = RegEnumKeyEx(key.hkey, index, - ctypes.cast(tmpbuf, ctypes.c_wchar_p), - ctypes.byref(length), - LPDWORD(), ctypes.c_wchar_p(), LPDWORD(), - ctypes.POINTER(FILETIME)()) - check_code(rc) - return ctypes.wstring_at(tmpbuf, length.value).rstrip(u'\x00') - -def QueryValueEx(key, value_name): - size = 256 - typ = DWORD() - while True: - tmp_size = DWORD(size) - buf = ctypes.create_string_buffer(size) - rc = RegQueryValueEx(key.hkey, value_name, LPDWORD(), - ctypes.byref(typ), - ctypes.cast(buf, LPBYTE), ctypes.byref(tmp_size)) - if rc != ERROR_MORE_DATA: - break - - size *= 2 - check_code(rc) - return (Reg2Py(buf, tmp_size.value, typ.value), typ.value) - -__all__ = ['OpenKey', 'QueryInfoKey', 'EnumValue', 'EnumKey', 'QueryValueEx', - 'HKEY_CLASSES_ROOT', 'HKEY_CURRENT_USER', 'HKEY_LOCAL_MACHINE', - 'HKEY_USERS', 'HKEY_PERFORMANCE_DATA', 'HKEY_CURRENT_CONFIG', - 'HKEY_DYN_DATA', 'REG_NONE', 'REG_SZ', 'REG_EXPAND_SZ', - 'REG_BINARY', 'REG_DWORD', 'REG_DWORD_BIG_ENDIAN', - 'REG_DWORD_LITTLE_ENDIAN', 'REG_LINK', 'REG_MULTI_SZ', - 'REG_RESOURCE_LIST', 'REG_FULL_RESOURCE_DESCRIPTOR', - 'REG_RESOURCE_REQUIREMENTS_LIST'] \ No newline at end of file diff --git a/DeDRM_plugin/alfcrypto.py b/DeDRM_plugin/alfcrypto.py index 3b9f319..6513fad 100644 --- a/DeDRM_plugin/alfcrypto.py +++ b/DeDRM_plugin/alfcrypto.py @@ -26,10 +26,7 @@ class Pukall_Cipher(object): raise Exception("PC1: Bad key length") wkey = [] for i in range(8): - if sys.version_info[0] == 2: - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - else: - wkey.append(key[i*2]<<8 | key[i*2+1]) + wkey.append(key[i*2]<<8 | key[i*2+1]) dst = bytearray(len(src)) for i in range(len(src)): temp1 = 0; @@ -42,10 +39,7 @@ class Pukall_Cipher(object): temp1 = (temp1*20021+1)&0xFFFF byteXorVal ^= temp1 ^ sum2 - if sys.version_info[0] == 2: - curByte = ord(src[i]) - else: - curByte = src[i] + curByte = src[i] if not decryption: keyXorVal = curByte * 257; @@ -55,11 +49,9 @@ class Pukall_Cipher(object): for j in range(8): wkey[j] ^= keyXorVal; - if sys.version_info[0] == 2: - dst[i] = chr(curByte) - else: - dst[i] = curByte - + dst[i] = curByte + + return bytes(dst) class Topaz_Cipher(object): diff --git a/DeDRM_plugin/androidkindlekey.py b/DeDRM_plugin/androidkindlekey.py index 27124bd..8ae8372 100755 --- a/DeDRM_plugin/androidkindlekey.py +++ b/DeDRM_plugin/androidkindlekey.py @@ -51,10 +51,7 @@ STORAGE2 = "map_data_storage.db" def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] diff --git a/DeDRM_plugin/argv_utils.py b/DeDRM_plugin/argv_utils.py index 2ca4776..2788643 100644 --- a/DeDRM_plugin/argv_utils.py +++ b/DeDRM_plugin/argv_utils.py @@ -3,46 +3,13 @@ import sys -# get sys.argv arguments and encode them into utf-8 +# get sys.argv arguments as a list of Unicode strings def unicode_argv(default_name): - - try: - from calibre.constants import iswindows - except: - iswindows = sys.platform.startswith('win') - - if iswindows: - # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode - # strings. - - # Versions 2.x of Python don't support Unicode in sys.argv on - # Windows, with the underlying Windows API instead replacing multi-byte - # characters with '?'. - - - from ctypes import POINTER, byref, cdll, c_int, windll - from ctypes.wintypes import LPCWSTR, LPWSTR - - GetCommandLineW = cdll.kernel32.GetCommandLineW - GetCommandLineW.argtypes = [] - GetCommandLineW.restype = LPCWSTR - - CommandLineToArgvW = windll.shell32.CommandLineToArgvW - CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] - CommandLineToArgvW.restype = POINTER(LPWSTR) - - cmd = GetCommandLineW() - argc = c_int(0) - argv = CommandLineToArgvW(cmd, byref(argc)) - if argc.value > 0: - # Remove Python executable and commands if present - start = argc.value - len(sys.argv) - return [argv[i] for i in - range(start, argc.value)] - # if we don't have any arguments at all, just pass back script name - # this should never happen - return [ default_name ] - else: - argvencoding = sys.stdin.encoding or "utf-8" - return [arg if (isinstance(arg, str) or isinstance(arg,unicode)) else str(arg, argvencoding) for arg in sys.argv] + # On Python 3 sys.argv is already a list of Unicode strings on every + # platform, so no special handling is needed. + if len(sys.argv) > 0: + return sys.argv + # if we don't have any arguments at all, just pass back the script name + # this should never happen + return [default_name] diff --git a/DeDRM_plugin/config.py b/DeDRM_plugin/config.py index 19b40fd..bd2b492 100755 --- a/DeDRM_plugin/config.py +++ b/DeDRM_plugin/config.py @@ -1143,13 +1143,7 @@ class AddAdeptDialog(): scriptpath = os.path.join(parent.parent.alfdir,"adobekey.py") defaultkeys, defaultnames = WineGetKeys(scriptpath, ".der",parent.getwineprefix()) - if sys.version_info[0] < 3: - # Python2 - import itertools - zip_function = itertools.izip - else: - # Python3 - zip_function = zip + zip_function = zip for key, name in zip_function(defaultkeys, defaultnames): key = codecs.encode(key,'hex').decode("latin-1") diff --git a/DeDRM_plugin/epubfontdecrypt.py b/DeDRM_plugin/epubfontdecrypt.py index c9a982f..ec10eab 100644 --- a/DeDRM_plugin/epubfontdecrypt.py +++ b/DeDRM_plugin/epubfontdecrypt.py @@ -16,8 +16,6 @@ Decrypts / deobfuscates font files in EPUB files """ -from __future__ import print_function - __license__ = 'GPL v3' __version__ = "2" diff --git a/DeDRM_plugin/erdr2pml.py b/DeDRM_plugin/erdr2pml.py index 2efe9bb..90cb429 100755 --- a/DeDRM_plugin/erdr2pml.py +++ b/DeDRM_plugin/erdr2pml.py @@ -142,9 +142,6 @@ def sanitizeFileName(name): def fixKey(key): def fixByte(b): - if sys.version_info[0] == 2: - b = ord(b) - return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80) return bytes(bytearray([fixByte(a) for a in key])) @@ -152,10 +149,7 @@ def deXOR(text, sp, table): r=b'' j = sp for i in range(len(text)): - if sys.version_info[0] == 2: - r += chr(ord(table[j]) ^ ord(text[i])) - else: - r += bytes(bytearray([table[j] ^ text[i]])) + r += bytes(bytearray([table[j] ^ text[i]])) j = j + 1 if j == len(table): j = 0 diff --git a/DeDRM_plugin/genbook.py b/DeDRM_plugin/genbook.py index 0d49f10..6df4ed4 100644 --- a/DeDRM_plugin/genbook.py +++ b/DeDRM_plugin/genbook.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # Python 3 for calibre 5.0 -from __future__ import print_function #@@CALIBRE_COMPAT_CODE@@ diff --git a/DeDRM_plugin/ignoblekeyAndroid.py b/DeDRM_plugin/ignoblekeyAndroid.py index e0b2f23..3e59e7f 100644 --- a/DeDRM_plugin/ignoblekeyAndroid.py +++ b/DeDRM_plugin/ignoblekeyAndroid.py @@ -16,10 +16,7 @@ import hashlib from lxml import etree def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] diff --git a/DeDRM_plugin/ignoblekeyNookStudy.py b/DeDRM_plugin/ignoblekeyNookStudy.py index 7916c39..2ffdadd 100644 --- a/DeDRM_plugin/ignoblekeyNookStudy.py +++ b/DeDRM_plugin/ignoblekeyNookStudy.py @@ -46,36 +46,22 @@ def getNookLogFiles(): logFiles = [] found = False if iswindows: - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg # some 64 bit machines do not have the proper registry key for some reason # or the python interface to the 32 vs 64 bit registry is broken paths = set() if 'LOCALAPPDATA' in os.environ.keys(): - # Python 2.x does not return unicode env. Use Python 3.x - if sys.version_info[0] == 2: - path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") - else: - path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") + path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") if os.path.isdir(path): paths.add(path) if 'USERPROFILE' in os.environ.keys(): - # Python 2.x does not return unicode env. Use Python 3.x - if sys.version_info[0] == 2: - path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local" - else: - path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local" + path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local" if os.path.isdir(path): paths.add(path) - if sys.version_info[0] == 2: - path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming" - else: - path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming" + path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming" if os.path.isdir(path): paths.add(path) # User Shell Folders show take precedent over Shell Folders if present diff --git a/DeDRM_plugin/ignoblekeyWindowsStore.py b/DeDRM_plugin/ignoblekeyWindowsStore.py index 29df204..ab0b624 100644 --- a/DeDRM_plugin/ignoblekeyWindowsStore.py +++ b/DeDRM_plugin/ignoblekeyWindowsStore.py @@ -22,10 +22,7 @@ import hashlib from lxml import etree def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] diff --git a/DeDRM_plugin/ineptepub.py b/DeDRM_plugin/ineptepub.py index 3802f71..d9ffac2 100644 --- a/DeDRM_plugin/ineptepub.py +++ b/DeDRM_plugin/ineptepub.py @@ -63,10 +63,7 @@ except ImportError: def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] diff --git a/DeDRM_plugin/ineptpdf.py b/DeDRM_plugin/ineptpdf.py index 100dff7..3ab4524 100755 --- a/DeDRM_plugin/ineptpdf.py +++ b/DeDRM_plugin/ineptpdf.py @@ -85,10 +85,7 @@ except ImportError: def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] @@ -147,10 +144,7 @@ def nunpack(s, default=0): elif l == 2: return struct.unpack('>H', s)[0] elif l == 3: - if sys.version_info[0] == 2: - return struct.unpack('>L', '\x00'+s)[0] - else: - return struct.unpack('>L', bytes([0]) + s)[0] + return struct.unpack('>L', bytes([0]) + s)[0] elif l == 4: return struct.unpack('>L', s)[0] else: @@ -414,10 +408,7 @@ class PSBaseParser(object): self.hex += c return (self.parse_literal_hex, i+1) if self.hex: - if sys.version_info[0] == 2: - self.token += chr(int(self.hex, 16)) - else: - self.token += bytes([int(self.hex, 16)]) + self.token += bytes([int(self.hex, 16)]) return (self.parse_literal, i) def parse_number(self, s, i): @@ -501,18 +492,12 @@ class PSBaseParser(object): self.oct += c return (self.parse_string_1, i+1) if self.oct: - if sys.version_info[0] == 2: - self.token += chr(int(self.oct, 8)) - else: - self.token += bytes([int(self.oct, 8)]) + self.token += bytes([int(self.oct, 8)]) return (self.parse_string, i) if c in ESC_STRING: - if sys.version_info[0] == 2: - self.token += chr(ESC_STRING[c]) - else: - self.token += bytes([ESC_STRING[c]]) - + self.token += bytes([ESC_STRING[c]]) + return (self.parse_string, i+1) def parse_wopen(self, s, i): @@ -551,12 +536,8 @@ class PSBaseParser(object): return (self.parse_hexstring, len(s)) j = m.start(0) self.token += s[i:j] - if sys.version_info[0] == 2: - token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)), - SPC.sub('', self.token)) - else: - token = HEX_PAIR.sub(lambda m: bytes([int(m.group(0), 16)]), - SPC.sub(b'', self.token)) + token = HEX_PAIR.sub(lambda m: bytes([int(m.group(0), 16)]), + SPC.sub(b'', self.token)) self.add_token(token) return (self.parse_main, j) @@ -577,10 +558,7 @@ class PSBaseParser(object): while 1: self.fillbuf() if eol: - if sys.version_info[0] == 2: - c = self.buf[self.charpos] - else: - c = bytes([self.buf[self.charpos]]) + c = bytes([self.buf[self.charpos]]) # handle '\r\n' if c == b'\n': @@ -591,17 +569,11 @@ class PSBaseParser(object): if m: linebuf += self.buf[self.charpos:m.end(0)] self.charpos = m.end(0) - if sys.version_info[0] == 2: - if linebuf[-1] == b'\r': - eol = True - else: - break - else: - if bytes([linebuf[-1]]) == b'\r': - eol = True - else: - break - + if bytes([linebuf[-1]]) == b'\r': + eol = True + else: + break + else: linebuf += self.buf[self.charpos:] self.charpos = len(self.buf) @@ -977,14 +949,9 @@ class PDFStream(PDFObject): for i in range(0, len(data), columns+1): pred = data[i] ent1 = data[i+1:i+1+columns] - if sys.version_info[0] == 2: - if pred == '\x02': - ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \ - for (a,b) in zip(ent0,ent1)) - else: - if pred == 2: - ent1 = b''.join(bytes([(a+b) & 255]) \ - for (a,b) in zip(ent0,ent1)) + if pred == 2: + ent1 = b''.join(bytes([(a+b) & 255]) \ + for (a,b) in zip(ent0,ent1)) buf += ent1 ent0 = ent1 data = buf @@ -1440,10 +1407,7 @@ class PDFDocument(object): x = ARC4.new(hash).decrypt(Odata) # 4 if R >= 3: for i in range(1,19+1): - if sys.version_info[0] == 2: - k = b''.join(chr(ord(c) ^ i) for c in hash ) - else: - k = b''.join(bytes([c ^ i]) for c in hash ) + k = b''.join(bytes([c ^ i]) for c in hash ) x = ARC4.new(k).decrypt(x) @@ -1510,10 +1474,7 @@ class PDFDocument(object): hash.update(docid[0]) # 3 x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4 for i in range(1,19+1): - if sys.version_info[0] == 2: - k = b''.join(chr(ord(c) ^ i) for c in key ) - else: - k = b''.join(bytes([c ^ i]) for c in key ) + k = b''.join(bytes([c ^ i]) for c in key ) x = ARC4.new(k).decrypt(x) u1 = x+x # 32bytes total @@ -1785,10 +1746,7 @@ class PDFDocument(object): data = data[16:] plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data) # remove pkcs#5 aes padding - if sys.version_info[0] == 2: - cutter = -1 * ord(plaintext[-1]) - else: - cutter = -1 * plaintext[-1] + cutter = -1 * plaintext[-1] plaintext = plaintext[:cutter] return plaintext diff --git a/DeDRM_plugin/k4mobidedrm.py b/DeDRM_plugin/k4mobidedrm.py index 4a22318..268b22f 100644 --- a/DeDRM_plugin/k4mobidedrm.py +++ b/DeDRM_plugin/k4mobidedrm.py @@ -69,10 +69,7 @@ import getopt import re import traceback import time -try: - import html.entities as htmlentitydefs -except: - import htmlentitydefs +import html.entities as htmlentitydefs import json diff --git a/DeDRM_plugin/kgenpids.py b/DeDRM_plugin/kgenpids.py index 670ed93..af93403 100644 --- a/DeDRM_plugin/kgenpids.py +++ b/DeDRM_plugin/kgenpids.py @@ -53,10 +53,7 @@ def SHA1(message): def encode(data, map): result = b'' for char in data: - if sys.version_info[0] == 2: - value = ord(char) - else: - value = char + value = char Q = (value ^ 0x80) // len(map) R = value % len(map) @@ -90,11 +87,9 @@ def decode(data,map): def getTwoBitsFromBitField(bitField,offset): byteNumber = 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 def getSixBitsFromBitField(bitField,offset): offset *= 3 @@ -171,10 +166,7 @@ def pidFromSerial(s, l): crc = crc32(s) arr1 = [0]*l 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] for i in range(l): arr1[i] ^= crc_bytes[i&3] @@ -190,10 +182,6 @@ def getKindlePids(rec209, token, serialnum): if isinstance(serialnum,str): serialnum = serialnum.encode('utf-8') - if sys.version_info[0] == 2: - if isinstance(serialnum,unicode): - serialnum = serialnum.encode('utf-8') - if rec209 is None: return [serialnum] diff --git a/DeDRM_plugin/kindlekey.py b/DeDRM_plugin/kindlekey.py index 9dc5381..0da4f0f 100644 --- a/DeDRM_plugin/kindlekey.py +++ b/DeDRM_plugin/kindlekey.py @@ -119,10 +119,7 @@ def primes(n): def encode(data, map): result = b'' for char in data: - if sys.version_info[0] == 2: - value = ord(char) - else: - value = char + value = char Q = (value ^ 0x80) // len(map) R = value % len(map) @@ -160,10 +157,7 @@ if iswindows: create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ string_at, Structure, c_void_p, cast - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg MAX_PATH = 255 kernel32 = windll.kernel32 @@ -242,14 +236,9 @@ if iswindows: # replace any non-ASCII values with 0xfffd for i in range(0,len(buffer)): - if sys.version_info[0] == 2: - if buffer[i]>u"\u007f": - #print "swapping char "+str(i)+" ("+buffer[i]+")" - buffer[i] = u"\ufffd" - else: - if buffer[i]>"\u007f": - #print "swapping char "+str(i)+" ("+buffer[i]+")" - buffer[i] = "\ufffd" + if buffer[i]>"\u007f": + #print "swapping char "+str(i)+" ("+buffer[i]+")" + buffer[i] = "\ufffd" # return utf-8 encoding of modified username #print "modified username:"+buffer.value return buffer.value.encode('utf-8') @@ -293,11 +282,7 @@ if iswindows: # or the python interface to the 32 vs 64 bit registry is broken path = "" if 'LOCALAPPDATA' in os.environ.keys(): - # Python 2.x does not return unicode env. Use Python 3.x - if sys.version_info[0] == 2: - path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") - else: - path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") + path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") # this is just another alternative. # path = getEnvironmentVariable('LOCALAPPDATA') if not os.path.isdir(path): diff --git a/DeDRM_plugin/kindlepid.py b/DeDRM_plugin/kindlepid.py index a851ee5..3700fe9 100644 --- a/DeDRM_plugin/kindlepid.py +++ b/DeDRM_plugin/kindlepid.py @@ -44,10 +44,7 @@ def pidFromSerial(s, l): arr1 = [0]*l 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] for i in range(l): diff --git a/DeDRM_plugin/mobidedrm.py b/DeDRM_plugin/mobidedrm.py index 864c08f..96c20ba 100755 --- a/DeDRM_plugin/mobidedrm.py +++ b/DeDRM_plugin/mobidedrm.py @@ -5,7 +5,6 @@ # Copyright © 2008 The Dark Reverser # Portions © 2008–2020 Apprentice Harper et al. -from __future__ import print_function __license__ = 'GPL v3' __version__ = "1.1" @@ -134,10 +133,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): if size <= 0: return result while True: - if sys.version_info[0] == 2: - v = ord(ptr[size-1]) - else: - v = ptr[size-1] + v = ptr[size-1] result |= (v & 0x7F) << bitpos bitpos += 7 @@ -154,10 +150,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): # if multibyte data is included in the encryped data, we'll # have already cleared this flag. if flags & 1: - if sys.version_info[0] == 2: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - else: - num += (ptr[size - num - 1] & 0x3) + 1 + num += (ptr[size - num - 1] & 0x3) + 1 return num @@ -316,10 +309,7 @@ class MobiBook: for pid in pidlist: bigpid = pid.encode('utf-8').ljust(16,b'\0') temp_key = PC1(keyvec1, bigpid, False) - if sys.version_info[0] == 2: - temp_key_sum = sum(map(ord,temp_key)) & 0xff - else: - temp_key_sum = sum(temp_key) & 0xff + temp_key_sum = sum(temp_key) & 0xff found_key = None for i in range(count): verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) @@ -335,11 +325,8 @@ class MobiBook: # Then try the default encoding that doesn't require a PID pid = '00000000' temp_key = keyvec1 - if sys.version_info[0] == 2: - temp_key_sum = sum(map(ord,temp_key)) & 0xff - else: - temp_key_sum = sum(temp_key) & 0xff - + temp_key_sum = sum(temp_key) & 0xff + for i in range(count): verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) if cksum == temp_key_sum: diff --git a/DeDRM_plugin/standalone/__init__.py b/DeDRM_plugin/standalone/__init__.py index 60b615b..ec35021 100644 --- a/DeDRM_plugin/standalone/__init__.py +++ b/DeDRM_plugin/standalone/__init__.py @@ -3,8 +3,6 @@ # CLI interface for the DeDRM plugin (useable without Calibre, too) -from __future__ import absolute_import, print_function - # Copyright © 2021 NoDRM """ diff --git a/DeDRM_plugin/standalone/jsonconfig.py b/DeDRM_plugin/standalone/jsonconfig.py index b7603df..c239ce3 100644 --- a/DeDRM_plugin/standalone/jsonconfig.py +++ b/DeDRM_plugin/standalone/jsonconfig.py @@ -4,8 +4,6 @@ # CLI interface for the DeDRM plugin (useable without Calibre, too) # Config implementation -from __future__ import absolute_import, print_function - # Taken from Calibre code - Copyright © 2008, Kovid Goyal kovid@kovidgoyal.net, GPLv3 """ diff --git a/DeDRM_plugin/standalone/passhash.py b/DeDRM_plugin/standalone/passhash.py index 028828d..fd04611 100644 --- a/DeDRM_plugin/standalone/passhash.py +++ b/DeDRM_plugin/standalone/passhash.py @@ -4,8 +4,6 @@ # CLI interface for the DeDRM plugin (useable without Calibre, too) # Adobe PassHash implementation -from __future__ import absolute_import, print_function - # Copyright © 2021 NoDRM """ diff --git a/DeDRM_plugin/standalone/remove_drm.py b/DeDRM_plugin/standalone/remove_drm.py index a67bc6f..e1305c9 100644 --- a/DeDRM_plugin/standalone/remove_drm.py +++ b/DeDRM_plugin/standalone/remove_drm.py @@ -4,8 +4,6 @@ # CLI interface for the DeDRM plugin (useable without Calibre, too) # DRM removal -from __future__ import absolute_import, print_function - # Copyright © 2021 NoDRM """ diff --git a/DeDRM_plugin/topazextract.py b/DeDRM_plugin/topazextract.py index 4f2c057..59b892d 100644 --- a/DeDRM_plugin/topazextract.py +++ b/DeDRM_plugin/topazextract.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from __future__ import print_function # topazextract.py # Mostly written by some_updates based on code from many others diff --git a/DeDRM_plugin/utilities.py b/DeDRM_plugin/utilities.py index 50a7de8..c30c451 100644 --- a/DeDRM_plugin/utilities.py +++ b/DeDRM_plugin/utilities.py @@ -9,12 +9,8 @@ __license__ = 'GPL v3' def uStrCmp (s1, s2, caseless=False): import unicodedata as ud - if sys.version_info[0] == 2: - str1 = s1 if isinstance(s1, unicode) else unicode(s1) - str2 = s2 if isinstance(s2, unicode) else unicode(s2) - else: - str1 = s1 if isinstance(s1, str) else str(s1) - str2 = s2 if isinstance(s2, str) else str(s2) + str1 = s1 if isinstance(s1, str) else str(s1) + str2 = s2 if isinstance(s2, str) else str(s2) if caseless: return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower()) diff --git a/Obok_plugin/__init__.py b/Obok_plugin/__init__.py index 864ded3..2f83437 100644 --- a/Obok_plugin/__init__.py +++ b/Obok_plugin/__init__.py @@ -1,7 +1,4 @@ # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import (unicode_literals, division, absolute_import, - print_function) - __license__ = 'GPL v3' __version__ = '10.0.9' __docformat__ = 'restructuredtext en' diff --git a/Obok_plugin/common_utils.py b/Obok_plugin/common_utils.py index fe67f42..ec63c87 100644 --- a/Obok_plugin/common_utils.py +++ b/Obok_plugin/common_utils.py @@ -411,7 +411,7 @@ class KeyValueComboBox(QComboBox): def populate_combo(self, selected_key): self.clear() selected_idx = idx = -1 - for key, value in self.values.iteritems(): + for key, value in self.values.items(): idx = idx + 1 self.addItem(value) if key == selected_key: @@ -419,7 +419,7 @@ class KeyValueComboBox(QComboBox): self.setCurrentIndex(selected_idx) def selected_key(self): - for key, value in self.values.iteritems(): + for key, value in self.values.items(): if value == self.currentText().strip(): return key @@ -442,7 +442,7 @@ class KeyComboBox(QComboBox): self.setCurrentIndex(selected_idx) def selected_key(self): - for key, value in self.values.iteritems(): + for key, value in self.values.items(): if key == self.currentText().strip(): return key diff --git a/Obok_plugin/dialogs.py b/Obok_plugin/dialogs.py index 16bfcb0..ffb8625 100644 --- a/Obok_plugin/dialogs.py +++ b/Obok_plugin/dialogs.py @@ -1,7 +1,4 @@ # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import (unicode_literals, division, absolute_import, - print_function) - __license__ = 'GPL v3' __docformat__ = 'restructuredtext en' diff --git a/Obok_plugin/obok/legacy_obok.py b/Obok_plugin/obok/legacy_obok.py index 9a9ae5d..caf24f2 100644 --- a/Obok_plugin/obok/legacy_obok.py +++ b/Obok_plugin/obok/legacy_obok.py @@ -42,10 +42,7 @@ class legacy_obok(object): pwsdid = '' try: if sys.platform.startswith('win'): - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg regkey_browser = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Kobo\\Kobo Desktop Edition\\Browser') cookies = winreg.QueryValueEx(regkey_browser, 'cookies') bytearrays = cookies[0] diff --git a/Obok_plugin/obok/obok.py b/Obok_plugin/obok/obok.py index a8379b1..b28be8f 100644 --- a/Obok_plugin/obok/obok.py +++ b/Obok_plugin/obok/obok.py @@ -166,7 +166,6 @@ # after all. # """Manage all Kobo books, either encrypted or DRM-free.""" -from __future__ import print_function __version__ = '10.0.9' __about__ = "Obok v{0}\nCopyright © 2012-2023 Physisticated et al.".format(__version__) @@ -192,10 +191,7 @@ except ImportError: from Crypto.Cipher import AES def unpad(data, padding=16): - if sys.version_info[0] == 2: - pad_len = ord(data[-1]) - else: - pad_len = data[-1] + pad_len = data[-1] return data[:-pad_len] @@ -224,8 +220,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,str) or isinstance(data,unicode): - # str for Python3, unicode for Python2 + if isinstance(data,str): data = data.encode(self.encoding,"replace") try: buffer = getattr(self.stream, 'buffer', self.stream) @@ -305,24 +300,13 @@ class KoboLibrary(object): if (self.kobodir == u""): if sys.platform.startswith('win'): - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg if sys.getwindowsversion().major > 5: if 'LOCALAPPDATA' in os.environ.keys(): - # Python 2.x does not return unicode env. Use Python 3.x - if sys.version_info[0] == 2: - self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") - else: - self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") + self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%") if (self.kobodir == u""): if 'USERPROFILE' in os.environ.keys(): - # Python 2.x does not return unicode env. Use Python 3.x - if sys.version_info[0] == 2: - self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), "Local Settings", "Application Data") - else: - self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data") + self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data") self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition") elif sys.platform.startswith('darwin'): self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition") diff --git a/Obok_plugin/utilities.py b/Obok_plugin/utilities.py index ba0d7fc..773e5d2 100644 --- a/Obok_plugin/utilities.py +++ b/Obok_plugin/utilities.py @@ -7,10 +7,7 @@ __docformat__ = 'restructuredtext en' import os, struct, time -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +from io import StringIO from traceback import print_exc from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem) -- 2.47.3 From 38dedea32bc6d5e8c83d4247c1955307b080ec37 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Mon, 22 Jun 2026 19:52:56 -0500 Subject: [PATCH 04/14] Fix invalid string escape sequences Python 3.12+ emits SyntaxWarning for unrecognised backslash escapes (and these will eventually become SyntaxErrors). Convert the affected string literals to raw strings so the escapes are explicit: - scriptinterface.py: filename filter regexes (\.der, \.b64, ...). - obok.py: MAC-address detection regexes (\s, \-) and the title sanitiser ([^\s\w]). - flatxml2svg.py: the emitted JavaScript snippet contains \? and \d but also a trailing newline, so the regex part is made a raw string and the "\n" is concatenated separately. All changes are value-preserving (an unrecognised escape already evaluated to the backslash plus the character); verified the emitted JS string and the compiled regexes are unchanged, and that both plugins now compile with zero SyntaxWarnings. Co-Authored-By: Claude Opus 4.8 --- DeDRM_plugin/flatxml2svg.py | 2 +- DeDRM_plugin/scriptinterface.py | 14 +++++++------- Obok_plugin/obok/obok.py | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DeDRM_plugin/flatxml2svg.py b/DeDRM_plugin/flatxml2svg.py index 3768358..05e7da5 100644 --- a/DeDRM_plugin/flatxml2svg.py +++ b/DeDRM_plugin/flatxml2svg.py @@ -199,7 +199,7 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array mlst.append('\n') mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) mlst.append('