summaryrefslogtreecommitdiffstats
path: root/third_party/rust/zip
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/zip
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/zip')
-rw-r--r--third_party/rust/zip/.cargo-checksum.json1
-rw-r--r--third_party/rust/zip/CHANGELOG.md9
-rw-r--r--third_party/rust/zip/CODE_OF_CONDUCT.md76
-rw-r--r--third_party/rust/zip/Cargo.lock457
-rw-r--r--third_party/rust/zip/Cargo.toml123
-rw-r--r--third_party/rust/zip/LICENSE21
-rw-r--r--third_party/rust/zip/README.md98
-rw-r--r--third_party/rust/zip/benches/read_entry.rs43
-rw-r--r--third_party/rust/zip/benches/read_metadata.rs38
-rw-r--r--third_party/rust/zip/examples/extract.rs64
-rw-r--r--third_party/rust/zip/examples/extract_lorem.rs31
-rw-r--r--third_party/rust/zip/examples/file_info.rs54
-rw-r--r--third_party/rust/zip/examples/stdin_info.rs35
-rw-r--r--third_party/rust/zip/examples/write_dir.rs125
-rw-r--r--third_party/rust/zip/examples/write_sample.rs71
-rw-r--r--third_party/rust/zip/src/aes.rs185
-rw-r--r--third_party/rust/zip/src/aes_ctr.rs281
-rw-r--r--third_party/rust/zip/src/compression.rs207
-rw-r--r--third_party/rust/zip/src/cp437.rs204
-rw-r--r--third_party/rust/zip/src/crc32.rs100
-rw-r--r--third_party/rust/zip/src/lib.rs44
-rw-r--r--third_party/rust/zip/src/read.rs1312
-rw-r--r--third_party/rust/zip/src/result.rs98
-rw-r--r--third_party/rust/zip/src/spec.rs219
-rw-r--r--third_party/rust/zip/src/types.rs608
-rw-r--r--third_party/rust/zip/src/write.rs1467
-rw-r--r--third_party/rust/zip/src/zipcrypto.rs184
-rw-r--r--third_party/rust/zip/tests/aes_encryption.rs80
-rw-r--r--third_party/rust/zip/tests/data/aes_archive.zipbin0 -> 908 bytes
-rw-r--r--third_party/rust/zip/tests/data/comment_garbage.zipbin0 -> 46 bytes
-rw-r--r--third_party/rust/zip/tests/data/files_and_dirs.zipbin0 -> 462 bytes
-rw-r--r--third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zipbin0 -> 124 bytes
-rw-r--r--third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zipbin0 -> 212 bytes
-rw-r--r--third_party/rust/zip/tests/data/invalid_offset.zipbin0 -> 1241 bytes
-rw-r--r--third_party/rust/zip/tests/data/invalid_offset2.zipbin0 -> 117 bytes
-rw-r--r--third_party/rust/zip/tests/data/mimetype.zipbin0 -> 153 bytes
-rw-r--r--third_party/rust/zip/tests/data/zip64_demo.zipbin0 -> 224 bytes
-rw-r--r--third_party/rust/zip/tests/end_to_end.rs205
-rw-r--r--third_party/rust/zip/tests/invalid_date.rs24
-rw-r--r--third_party/rust/zip/tests/issue_234.rs31
-rw-r--r--third_party/rust/zip/tests/zip64_large.rs211
-rw-r--r--third_party/rust/zip/tests/zip_comment_garbage.rs30
-rw-r--r--third_party/rust/zip/tests/zip_crypto.rs85
43 files changed, 6821 insertions, 0 deletions
diff --git a/third_party/rust/zip/.cargo-checksum.json b/third_party/rust/zip/.cargo-checksum.json
new file mode 100644
index 0000000000..9766507ebc
--- /dev/null
+++ b/third_party/rust/zip/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"da022ffbfcbf04616fc8e31b7a38fd7c8f2ad47a7fb42750fc04de110cf95857","CODE_OF_CONDUCT.md":"77930b2129928632cae79e10d0863885b34729e4f144f3ae5443cff65997196a","Cargo.lock":"5c81aacae00a3838358fed3e453cbb128f9870162a280d91b77f20c7b4c94390","Cargo.toml":"69d71445861d823f30fbd330f74cb395ed1f4e981f3c61e4754eda2f1f1cb0b4","LICENSE":"6ac8711fb340c62ce0a4ecd463342d3fa0e8e70de697c863a2e1c0c53006003c","README.md":"4da0a964d8fb78a5caae4674b4f69fc24f080167af56927d1a5c15412f9f672d","benches/read_entry.rs":"700235ee560887f33da56895d2a4da901c070b2a6a59aef90331046af4ddadad","benches/read_metadata.rs":"591e5adb9d36bf66da21d7e0d36a129c9bcf1331fa11714df8c53c860aee65ff","examples/extract.rs":"81f58da293553fe019319348e1d226a2d192480667877e72d77ab58812f5de1b","examples/extract_lorem.rs":"2d438706ca6e02468f362a54f4f2125598d2be1051e7ce40b71be58cfd703669","examples/file_info.rs":"20639234293511022938c8204488d182ccbcdfb59d962000acd8ac8f2f6db667","examples/stdin_info.rs":"5735801c9692a9b6ec35da6e6a65807db48b577068ab9df0018343df5161034b","examples/write_dir.rs":"224239e040c583f2e1ca4b1328ebe1a63b3c2213df6da8232cd62e004db05587","examples/write_sample.rs":"f723bf69676492599c2684aa995177f80e2aa922f1c9ae1681ed39e0a15959ad","src/aes.rs":"c1b19bdf15c75cc27b5657d1dc574b5c5ebcdc7ca2573e9909cc6ba1ffe7f6d8","src/aes_ctr.rs":"6a7a07c9538daf53624818f82084c6655db3862ca00751f3679c8b0bc346b70d","src/compression.rs":"84f8ded41182155cfbdb1ce79b9dc84cae0a6e55557021cf9f2d7168d8913b48","src/cp437.rs":"e3a044ddefc947f6d9c26ba50ebd438955d90b8971ad8b15e5dd1194495f3b93","src/crc32.rs":"6c5a48834dfa1ab33590fcc63f4121ee6d3f8fbcc5018e905c4d8570096874cf","src/lib.rs":"7c2f6bc3dde9575eeeaef9569139f28351333c29362f74569e627ad7be5a10b2","src/read.rs":"1c8e60c37a7016399d5bdf3796bcd4ad8a5cdf7450498233c7d056fd7533692a","src/result.rs":"7dc62da507ef2228313ed617d01f4d207eacb0b66352a497d24f04298e90e5df","src/spec.rs":"2a249aeb3c58ca475b5c7da6919cb61ac3cbe07b94a0cb3c730646d46fb265a8","src/types.rs":"21a37a8632bd9b2572a7ccd19e6048dcc453a3c4fd7583f3eddefbec7dd641a5","src/write.rs":"16ccc971f2858042d2f92e0448a72849745ea0a25449fd7c8961b78a466b6e6e","src/zipcrypto.rs":"4fe362af2a8e419d9cff6acc165acdc0dccaacc53baec7269b5f55dfcd396f5c","tests/aes_encryption.rs":"3733df1f5dcab4e9f9b9ec25cc7660f23c917759670e9b2e6fe77cfcd771faa9","tests/data/aes_archive.zip":"4abb3f304d1ab669453b7c4eae80db6ce8aff4ab91c8ab9a6edf90bbfede12f4","tests/data/comment_garbage.zip":"4a908af18691e50fc7773b34669a8ab6cb60875bee4d60fb7cd66d90eed55d2b","tests/data/files_and_dirs.zip":"e54cd8d084739f8d0b119d769c1263507b4da915cc38e4e2d3e4ef17c718f317","tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip":"c680ed5ad622aae23eea3b5d90145cb79550e413cc6e9a7b67bb1043851512ee","tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip":"7c272255e825aff7167dc8d1f2e1d16583e58347bb56ecf9879bdb07c6ad18e9","tests/data/invalid_offset.zip":"c5534a1803145f6344b04c437d436bd583852c78dd3937f4a73a2a39aa2d76b2","tests/data/invalid_offset2.zip":"0f0f3d654f303eaf90f599c0b0266be476ce34857cff932070a86e9853dcb4f9","tests/data/mimetype.zip":"aad4289745bd89016181a35653c9a483c6b9b632e06d002736c7040d61c562a2","tests/data/zip64_demo.zip":"223072a480f60d6a700e427b294e575ae2d6265f3e9881b647927f31d96c7e01","tests/end_to_end.rs":"ba69c247e2f555a954ae97615a46a9b21092671f7ff1abebaba058b30a53bb66","tests/invalid_date.rs":"72d5425ffe2887e05646ba56f5116ac760cbeeb7889da5c9be612fe57477bc94","tests/issue_234.rs":"1a3d68d79108140100709c188841c6bf54af8500c819072a440ec72df5d8ab26","tests/zip64_large.rs":"d4859f551be83c2ae3bd4a8e37c7e617fb54bb27310e725de6c7076c5e566d6b","tests/zip_comment_garbage.rs":"ab0e2e3ace1ca5459977a092ee96836f58c7f7a7cfc8de258ab448d8d24f1cbb","tests/zip_crypto.rs":"7f4f9670e5d971ac4234c7ab2cafecf43801baf0be0cfca6e34c651f6fc6a80d"},"package":"0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef"} \ No newline at end of file
diff --git a/third_party/rust/zip/CHANGELOG.md b/third_party/rust/zip/CHANGELOG.md
new file mode 100644
index 0000000000..cd79e3913f
--- /dev/null
+++ b/third_party/rust/zip/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+## [0.6.4]
+
+### Changed
+
+ - [#333](https://github.com/zip-rs/zip/pull/333): disabled the default features of the `time` dependency, and also `formatting` and `macros`, as they were enabled by mistake.
+ - Deprecated [`DateTime::from_time`](https://docs.rs/zip/0.6/zip/struct.DateTime.html#method.from_time) in favor of [`DateTime::try_from`](https://docs.rs/zip/0.6/zip/struct.DateTime.html#impl-TryFrom-for-DateTime)
+ \ No newline at end of file
diff --git a/third_party/rust/zip/CODE_OF_CONDUCT.md b/third_party/rust/zip/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..2290ec2bf8
--- /dev/null
+++ b/third_party/rust/zip/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all project spaces, and it also applies when
+an individual is representing the project or its community in public spaces.
+Examples of representing a project or community include using an official
+project e-mail address, posting via an official social media account, or acting
+as an appointed representative at an online or offline event. Representation of
+a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at ryan.levick@gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/third_party/rust/zip/Cargo.lock b/third_party/rust/zip/Cargo.lock
new file mode 100644
index 0000000000..f0f4b2bd0e
--- /dev/null
+++ b/third_party/rust/zip/Cargo.lock
@@ -0,0 +1,457 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+ "opaque-debug",
+]
+
+[[package]]
+name = "base64ct"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
+
+[[package]]
+name = "bencher"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+dependencies = [
+ "crc32fast",
+ "libz-sys",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+
+[[package]]
+name = "jobserver"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+
+[[package]]
+name = "sha1"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "time"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+dependencies = [
+ "itoa",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zip"
+version = "0.6.4"
+dependencies = [
+ "aes",
+ "bencher",
+ "byteorder",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "getrandom",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "walkdir",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.5+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
diff --git a/third_party/rust/zip/Cargo.toml b/third_party/rust/zip/Cargo.toml
new file mode 100644
index 0000000000..7a1b656b23
--- /dev/null
+++ b/third_party/rust/zip/Cargo.toml
@@ -0,0 +1,123 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "zip"
+version = "0.6.4"
+authors = [
+ "Mathijs van de Nes <git@mathijs.vd-nes.nl>",
+ "Marli Frost <marli@frost.red>",
+ "Ryan Levick <ryan.levick@gmail.com>",
+]
+description = """
+Library to support the reading and writing of zip files.
+"""
+readme = "README.md"
+keywords = [
+ "zip",
+ "archive",
+]
+license = "MIT"
+repository = "https://github.com/zip-rs/zip.git"
+
+[[bench]]
+name = "read_entry"
+harness = false
+
+[[bench]]
+name = "read_metadata"
+harness = false
+
+[dependencies.aes]
+version = "0.7.5"
+optional = true
+
+[dependencies.byteorder]
+version = "1.4.3"
+
+[dependencies.bzip2]
+version = "0.4.3"
+optional = true
+
+[dependencies.constant_time_eq]
+version = "0.1.5"
+optional = true
+
+[dependencies.crc32fast]
+version = "1.3.2"
+
+[dependencies.flate2]
+version = "1.0.23"
+optional = true
+default-features = false
+
+[dependencies.hmac]
+version = "0.12.1"
+features = ["reset"]
+optional = true
+
+[dependencies.pbkdf2]
+version = "0.11.0"
+optional = true
+
+[dependencies.sha1]
+version = "0.10.1"
+optional = true
+
+[dependencies.time]
+version = "0.3.7"
+features = ["std"]
+optional = true
+default-features = false
+
+[dependencies.zstd]
+version = "0.11.2"
+optional = true
+
+[dev-dependencies.bencher]
+version = "0.1.5"
+
+[dev-dependencies.getrandom]
+version = "0.2.5"
+
+[dev-dependencies.time]
+version = "0.3.7"
+features = [
+ "formatting",
+ "macros",
+]
+
+[dev-dependencies.walkdir]
+version = "2.3.2"
+
+[features]
+aes-crypto = [
+ "aes",
+ "constant_time_eq",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+]
+default = [
+ "aes-crypto",
+ "bzip2",
+ "deflate",
+ "time",
+ "zstd",
+]
+deflate = ["flate2/rust_backend"]
+deflate-miniz = ["flate2/default"]
+deflate-zlib = ["flate2/zlib"]
+unreserved = []
+
+[target."cfg(any(all(target_arch = \"arm\", target_pointer_width = \"32\"), target_arch = \"mips\", target_arch = \"powerpc\"))".dependencies.crossbeam-utils]
+version = "0.8.8"
diff --git a/third_party/rust/zip/LICENSE b/third_party/rust/zip/LICENSE
new file mode 100644
index 0000000000..b2d7f7bbdc
--- /dev/null
+++ b/third_party/rust/zip/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Mathijs van de Nes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/third_party/rust/zip/README.md b/third_party/rust/zip/README.md
new file mode 100644
index 0000000000..3754a7c0f7
--- /dev/null
+++ b/third_party/rust/zip/README.md
@@ -0,0 +1,98 @@
+zip-rs
+======
+
+[![Build Status](https://img.shields.io/github/workflow/status/zip-rs/zip/CI)](https://github.com/zip-rs/zip/actions?query=branch%3Amaster+workflow%3ACI)
+[![Crates.io version](https://img.shields.io/crates/v/zip.svg)](https://crates.io/crates/zip)
+[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/rQ7H9cSsF4)
+
+[Documentation](https://docs.rs/zip/0.6.3/zip/)
+
+> PSA: This version of the ZIP crate will not gain any new features,
+> and will only be updated if major security issues are found.
+
+Info
+----
+
+
+A zip library for rust which supports reading and writing of simple ZIP files.
+
+Supported compression formats:
+
+* stored (i.e. none)
+* deflate
+* bzip2
+* zstd
+
+Currently unsupported zip extensions:
+
+* Encryption
+* Multi-disk
+
+Usage
+-----
+
+With all default features:
+
+```toml
+[dependencies]
+zip = "0.6.4"
+```
+
+Without the default features:
+
+```toml
+[dependencies]
+zip = { version = "0.6.4", default-features = false }
+```
+
+The features available are:
+
+* `aes-crypto`: Enables decryption of files which were encrypted with AES. Supports AE-1 and AE-2 methods.
+* `deflate`: Enables the deflate compression algorithm, which is the default for zip files.
+* `bzip2`: Enables the BZip2 compression algorithm.
+* `time`: Enables features using the [time](https://github.com/rust-lang-deprecated/time) crate.
+* `zstd`: Enables the Zstandard compression algorithm.
+
+All of these are enabled by default.
+
+MSRV
+----
+
+Our current Minimum Supported Rust Version is **1.59.0**. When adding features,
+we will follow these guidelines:
+
+- We will always support the latest four minor Rust versions. This gives you a 6
+ month window to upgrade your compiler.
+- Any change to the MSRV will be accompanied with a **minor** version bump
+ - While the crate is pre-1.0, this will be a change to the PATCH version.
+
+Examples
+--------
+
+See the [examples directory](examples) for:
+ * How to write a file to a zip.
+ * How to write a directory of files to a zip (using [walkdir](https://github.com/BurntSushi/walkdir)).
+ * How to extract a zip file.
+ * How to extract a single file from a zip.
+ * How to read a zip from the standard input.
+
+Fuzzing
+-------
+
+Fuzzing support is through [cargo fuzz](https://github.com/rust-fuzz/cargo-fuzz). To install cargo fuzz:
+
+```bash
+cargo install cargo-fuzz
+```
+
+To list fuzz targets:
+
+```bash
+cargo +nightly fuzz list
+```
+
+To start fuzzing zip extraction:
+
+```bash
+cargo +nightly fuzz run fuzz_read
+```
diff --git a/third_party/rust/zip/benches/read_entry.rs b/third_party/rust/zip/benches/read_entry.rs
new file mode 100644
index 0000000000..af9affe319
--- /dev/null
+++ b/third_party/rust/zip/benches/read_entry.rs
@@ -0,0 +1,43 @@
+use bencher::{benchmark_group, benchmark_main};
+
+use std::io::{Cursor, Read, Write};
+
+use bencher::Bencher;
+use getrandom::getrandom;
+use zip::{ZipArchive, ZipWriter};
+
+fn generate_random_archive(size: usize) -> Vec<u8> {
+ let data = Vec::new();
+ let mut writer = ZipWriter::new(Cursor::new(data));
+ let options =
+ zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
+
+ writer.start_file("random.dat", options).unwrap();
+ let mut bytes = vec![0u8; size];
+ getrandom(&mut bytes).unwrap();
+ writer.write_all(&bytes).unwrap();
+
+ writer.finish().unwrap().into_inner()
+}
+
+fn read_entry(bench: &mut Bencher) {
+ let size = 1024 * 1024;
+ let bytes = generate_random_archive(size);
+ let mut archive = ZipArchive::new(Cursor::new(bytes.as_slice())).unwrap();
+
+ bench.iter(|| {
+ let mut file = archive.by_name("random.dat").unwrap();
+ let mut buf = [0u8; 1024];
+ loop {
+ let n = file.read(&mut buf).unwrap();
+ if n == 0 {
+ break;
+ }
+ }
+ });
+
+ bench.bytes = size as u64;
+}
+
+benchmark_group!(benches, read_entry);
+benchmark_main!(benches);
diff --git a/third_party/rust/zip/benches/read_metadata.rs b/third_party/rust/zip/benches/read_metadata.rs
new file mode 100644
index 0000000000..95334b1c5a
--- /dev/null
+++ b/third_party/rust/zip/benches/read_metadata.rs
@@ -0,0 +1,38 @@
+use bencher::{benchmark_group, benchmark_main};
+
+use std::io::{Cursor, Write};
+
+use bencher::Bencher;
+use zip::{ZipArchive, ZipWriter};
+
+const FILE_COUNT: usize = 15_000;
+const FILE_SIZE: usize = 1024;
+
+fn generate_random_archive(count_files: usize, file_size: usize) -> Vec<u8> {
+ let data = Vec::new();
+ let mut writer = ZipWriter::new(Cursor::new(data));
+ let options =
+ zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
+
+ let bytes = vec![0u8; file_size];
+
+ for i in 0..count_files {
+ let name = format!("file_deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef_{i}.dat");
+ writer.start_file(name, options).unwrap();
+ writer.write_all(&bytes).unwrap();
+ }
+
+ writer.finish().unwrap().into_inner()
+}
+
+fn read_metadata(bench: &mut Bencher) {
+ let bytes = generate_random_archive(FILE_COUNT, FILE_SIZE);
+
+ bench.iter(|| {
+ let archive = ZipArchive::new(Cursor::new(bytes.as_slice())).unwrap();
+ archive.len()
+ });
+}
+
+benchmark_group!(benches, read_metadata);
+benchmark_main!(benches);
diff --git a/third_party/rust/zip/examples/extract.rs b/third_party/rust/zip/examples/extract.rs
new file mode 100644
index 0000000000..3080716266
--- /dev/null
+++ b/third_party/rust/zip/examples/extract.rs
@@ -0,0 +1,64 @@
+use std::fs;
+use std::io;
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+fn real_main() -> i32 {
+ let args: Vec<_> = std::env::args().collect();
+ if args.len() < 2 {
+ println!("Usage: {} <filename>", args[0]);
+ return 1;
+ }
+ let fname = std::path::Path::new(&*args[1]);
+ let file = fs::File::open(fname).unwrap();
+
+ let mut archive = zip::ZipArchive::new(file).unwrap();
+
+ for i in 0..archive.len() {
+ let mut file = archive.by_index(i).unwrap();
+ let outpath = match file.enclosed_name() {
+ Some(path) => path.to_owned(),
+ None => continue,
+ };
+
+ {
+ let comment = file.comment();
+ if !comment.is_empty() {
+ println!("File {i} comment: {comment}");
+ }
+ }
+
+ if (*file.name()).ends_with('/') {
+ println!("File {} extracted to \"{}\"", i, outpath.display());
+ fs::create_dir_all(&outpath).unwrap();
+ } else {
+ println!(
+ "File {} extracted to \"{}\" ({} bytes)",
+ i,
+ outpath.display(),
+ file.size()
+ );
+ if let Some(p) = outpath.parent() {
+ if !p.exists() {
+ fs::create_dir_all(p).unwrap();
+ }
+ }
+ let mut outfile = fs::File::create(&outpath).unwrap();
+ io::copy(&mut file, &mut outfile).unwrap();
+ }
+
+ // Get and Set permissions
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+
+ if let Some(mode) = file.unix_mode() {
+ fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap();
+ }
+ }
+ }
+
+ 0
+}
diff --git a/third_party/rust/zip/examples/extract_lorem.rs b/third_party/rust/zip/examples/extract_lorem.rs
new file mode 100644
index 0000000000..bc50abe164
--- /dev/null
+++ b/third_party/rust/zip/examples/extract_lorem.rs
@@ -0,0 +1,31 @@
+use std::io::prelude::*;
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+fn real_main() -> i32 {
+ let args: Vec<_> = std::env::args().collect();
+ if args.len() < 2 {
+ println!("Usage: {} <filename>", args[0]);
+ return 1;
+ }
+ let fname = std::path::Path::new(&*args[1]);
+ let zipfile = std::fs::File::open(fname).unwrap();
+
+ let mut archive = zip::ZipArchive::new(zipfile).unwrap();
+
+ let mut file = match archive.by_name("test/lorem_ipsum.txt") {
+ Ok(file) => file,
+ Err(..) => {
+ println!("File test/lorem_ipsum.txt not found");
+ return 2;
+ }
+ };
+
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).unwrap();
+ println!("{contents}");
+
+ 0
+}
diff --git a/third_party/rust/zip/examples/file_info.rs b/third_party/rust/zip/examples/file_info.rs
new file mode 100644
index 0000000000..6a2adc58ed
--- /dev/null
+++ b/third_party/rust/zip/examples/file_info.rs
@@ -0,0 +1,54 @@
+use std::fs;
+use std::io::BufReader;
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+fn real_main() -> i32 {
+ let args: Vec<_> = std::env::args().collect();
+ if args.len() < 2 {
+ println!("Usage: {} <filename>", args[0]);
+ return 1;
+ }
+ let fname = std::path::Path::new(&*args[1]);
+ let file = fs::File::open(fname).unwrap();
+ let reader = BufReader::new(file);
+
+ let mut archive = zip::ZipArchive::new(reader).unwrap();
+
+ for i in 0..archive.len() {
+ let file = archive.by_index(i).unwrap();
+ let outpath = match file.enclosed_name() {
+ Some(path) => path,
+ None => {
+ println!("Entry {} has a suspicious path", file.name());
+ continue;
+ }
+ };
+
+ {
+ let comment = file.comment();
+ if !comment.is_empty() {
+ println!("Entry {i} comment: {comment}");
+ }
+ }
+
+ if (*file.name()).ends_with('/') {
+ println!(
+ "Entry {} is a directory with name \"{}\"",
+ i,
+ outpath.display()
+ );
+ } else {
+ println!(
+ "Entry {} is a file with name \"{}\" ({} bytes)",
+ i,
+ outpath.display(),
+ file.size()
+ );
+ }
+ }
+
+ 0
+}
diff --git a/third_party/rust/zip/examples/stdin_info.rs b/third_party/rust/zip/examples/stdin_info.rs
new file mode 100644
index 0000000000..a609916a09
--- /dev/null
+++ b/third_party/rust/zip/examples/stdin_info.rs
@@ -0,0 +1,35 @@
+use std::io::{self, Read};
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+fn real_main() -> i32 {
+ let stdin = io::stdin();
+ let mut stdin_handle = stdin.lock();
+ let mut buf = [0u8; 16];
+
+ loop {
+ match zip::read::read_zipfile_from_stream(&mut stdin_handle) {
+ Ok(Some(mut file)) => {
+ println!(
+ "{}: {} bytes ({} bytes packed)",
+ file.name(),
+ file.size(),
+ file.compressed_size()
+ );
+ match file.read(&mut buf) {
+ Ok(n) => println!("The first {} bytes are: {:?}", n, &buf[0..n]),
+ Err(e) => println!("Could not read the file: {e:?}"),
+ };
+ }
+ Ok(None) => break,
+ Err(e) => {
+ println!("Error encountered while reading zip: {e:?}");
+ return 1;
+ }
+ }
+ }
+
+ 0
+}
diff --git a/third_party/rust/zip/examples/write_dir.rs b/third_party/rust/zip/examples/write_dir.rs
new file mode 100644
index 0000000000..3b043528f4
--- /dev/null
+++ b/third_party/rust/zip/examples/write_dir.rs
@@ -0,0 +1,125 @@
+use std::io::prelude::*;
+use std::io::{Seek, Write};
+use std::iter::Iterator;
+use zip::result::ZipError;
+use zip::write::FileOptions;
+
+use std::fs::File;
+use std::path::Path;
+use walkdir::{DirEntry, WalkDir};
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+const METHOD_STORED: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Stored);
+
+#[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+))]
+const METHOD_DEFLATED: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Deflated);
+#[cfg(not(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+)))]
+const METHOD_DEFLATED: Option<zip::CompressionMethod> = None;
+
+#[cfg(feature = "bzip2")]
+const METHOD_BZIP2: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Bzip2);
+#[cfg(not(feature = "bzip2"))]
+const METHOD_BZIP2: Option<zip::CompressionMethod> = None;
+
+#[cfg(feature = "zstd")]
+const METHOD_ZSTD: Option<zip::CompressionMethod> = Some(zip::CompressionMethod::Zstd);
+#[cfg(not(feature = "zstd"))]
+const METHOD_ZSTD: Option<zip::CompressionMethod> = None;
+
+fn real_main() -> i32 {
+ let args: Vec<_> = std::env::args().collect();
+ if args.len() < 3 {
+ println!(
+ "Usage: {} <source_directory> <destination_zipfile>",
+ args[0]
+ );
+ return 1;
+ }
+
+ let src_dir = &*args[1];
+ let dst_file = &*args[2];
+ for &method in [METHOD_STORED, METHOD_DEFLATED, METHOD_BZIP2, METHOD_ZSTD].iter() {
+ if method.is_none() {
+ continue;
+ }
+ match doit(src_dir, dst_file, method.unwrap()) {
+ Ok(_) => println!("done: {src_dir} written to {dst_file}"),
+ Err(e) => println!("Error: {e:?}"),
+ }
+ }
+
+ 0
+}
+
+fn zip_dir<T>(
+ it: &mut dyn Iterator<Item = DirEntry>,
+ prefix: &str,
+ writer: T,
+ method: zip::CompressionMethod,
+) -> zip::result::ZipResult<()>
+where
+ T: Write + Seek,
+{
+ let mut zip = zip::ZipWriter::new(writer);
+ let options = FileOptions::default()
+ .compression_method(method)
+ .unix_permissions(0o755);
+
+ let mut buffer = Vec::new();
+ for entry in it {
+ let path = entry.path();
+ let name = path.strip_prefix(Path::new(prefix)).unwrap();
+
+ // Write file or directory explicitly
+ // Some unzip tools unzip files with directory paths correctly, some do not!
+ if path.is_file() {
+ println!("adding file {path:?} as {name:?} ...");
+ #[allow(deprecated)]
+ zip.start_file_from_path(name, options)?;
+ let mut f = File::open(path)?;
+
+ f.read_to_end(&mut buffer)?;
+ zip.write_all(&buffer)?;
+ buffer.clear();
+ } else if !name.as_os_str().is_empty() {
+ // Only if not root! Avoids path spec / warning
+ // and mapname conversion failed error on unzip
+ println!("adding dir {path:?} as {name:?} ...");
+ #[allow(deprecated)]
+ zip.add_directory_from_path(name, options)?;
+ }
+ }
+ zip.finish()?;
+ Result::Ok(())
+}
+
+fn doit(
+ src_dir: &str,
+ dst_file: &str,
+ method: zip::CompressionMethod,
+) -> zip::result::ZipResult<()> {
+ if !Path::new(src_dir).is_dir() {
+ return Err(ZipError::FileNotFound);
+ }
+
+ let path = Path::new(dst_file);
+ let file = File::create(path).unwrap();
+
+ let walkdir = WalkDir::new(src_dir);
+ let it = walkdir.into_iter();
+
+ zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method)?;
+
+ Ok(())
+}
diff --git a/third_party/rust/zip/examples/write_sample.rs b/third_party/rust/zip/examples/write_sample.rs
new file mode 100644
index 0000000000..2e45cb1eac
--- /dev/null
+++ b/third_party/rust/zip/examples/write_sample.rs
@@ -0,0 +1,71 @@
+use std::io::prelude::*;
+use zip::write::FileOptions;
+
+fn main() {
+ std::process::exit(real_main());
+}
+
+fn real_main() -> i32 {
+ let args: Vec<_> = std::env::args().collect();
+ if args.len() < 2 {
+ println!("Usage: {} <filename>", args[0]);
+ return 1;
+ }
+
+ let filename = &*args[1];
+ match doit(filename) {
+ Ok(_) => println!("File written to {filename}"),
+ Err(e) => println!("Error: {e:?}"),
+ }
+
+ 0
+}
+
+fn doit(filename: &str) -> zip::result::ZipResult<()> {
+ let path = std::path::Path::new(filename);
+ let file = std::fs::File::create(path).unwrap();
+
+ let mut zip = zip::ZipWriter::new(file);
+
+ zip.add_directory("test/", Default::default())?;
+
+ let options = FileOptions::default()
+ .compression_method(zip::CompressionMethod::Stored)
+ .unix_permissions(0o755);
+ zip.start_file("test/☃.txt", options)?;
+ zip.write_all(b"Hello, World!\n")?;
+
+ zip.start_file("test/lorem_ipsum.txt", Default::default())?;
+ zip.write_all(LOREM_IPSUM)?;
+
+ zip.finish()?;
+ Ok(())
+}
+
+const LOREM_IPSUM : &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet
+molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex,
+dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor
+vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
+inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
+
+Phasellus sed nisi in augue sodales pulvinar ut et leo. Pellentesque eget leo vitae massa bibendum sollicitudin. Curabitur erat lectus, congue quis auctor sed, aliquet
+bibendum est. Ut porta ultricies turpis at maximus. Cras non lobortis justo. Duis rutrum magna sed velit facilisis, et sagittis metus laoreet. Pellentesque quam ligula,
+dapibus vitae mauris quis, dapibus cursus leo. Sed sit amet condimentum eros. Nulla vestibulum enim sit amet lorem pharetra, eu fringilla nisl posuere. Sed tristique non
+nibh at viverra. Vivamus sed accumsan lacus, nec pretium eros. Mauris elementum arcu eu risus fermentum, tempor ullamcorper neque aliquam. Sed tempor in erat eu
+suscipit. In euismod in libero in facilisis. Donec sagittis, odio et fermentum dignissim, risus justo pretium nibh, eget vestibulum lectus metus vel lacus.
+
+Quisque feugiat, magna ac feugiat ullamcorper, augue justo consequat felis, ut fermentum arcu lorem vitae ligula. Quisque iaculis tempor maximus. In quis eros ac tellus
+aliquam placerat quis id tellus. Donec non gravida nulla. Morbi faucibus neque sed faucibus aliquam. Sed accumsan mattis nunc, non interdum justo. Cras vitae facilisis
+leo. Fusce sollicitudin ultrices sagittis. Maecenas eget massa id lorem dignissim ultrices non et ligula. Pellentesque aliquam mi ac neque tempus ornare. Morbi non enim
+vulputate quam ullamcorper finibus id non neque. Quisque malesuada commodo lorem, ut ornare velit iaculis rhoncus. Mauris vel maximus ex.
+
+Morbi eleifend blandit diam, non vulputate ante iaculis in. Donec pellentesque augue id enim suscipit, eget suscipit lacus commodo. Ut vel ex vitae elit imperdiet
+vulputate. Nunc eu mattis orci, ut pretium sem. Nam vitae purus mollis ante tempus malesuada a at magna. Integer mattis lectus non luctus lobortis. In a cursus quam,
+eget faucibus sem.
+
+Donec vitae condimentum nisi, non efficitur massa. Praesent sed mi in massa sollicitudin iaculis. Pellentesque a libero ultrices, sodales lacus eu, ornare dui. In
+laoreet est nec dolor aliquam consectetur. Integer iaculis felis venenatis libero pulvinar, ut pretium odio interdum. Donec in nisi eu dolor varius vestibulum eget vel
+nunc. Morbi a venenatis quam, in vehicula justo. Nam risus dui, auctor eu accumsan at, sagittis ac lectus. Mauris iaculis dignissim interdum. Cras cursus dapibus auctor.
+Donec sagittis massa vitae tortor viverra vehicula. Mauris fringilla nunc eu lorem ultrices placerat. Maecenas posuere porta quam at semper. Praesent eu bibendum eros.
+Nunc congue sollicitudin ante, sollicitudin lacinia magna cursus vitae.
+";
diff --git a/third_party/rust/zip/src/aes.rs b/third_party/rust/zip/src/aes.rs
new file mode 100644
index 0000000000..8997705c69
--- /dev/null
+++ b/third_party/rust/zip/src/aes.rs
@@ -0,0 +1,185 @@
+//! Implementation of the AES decryption for zip files.
+//!
+//! This was implemented according to the [WinZip specification](https://www.winzip.com/win/en/aes_info.html).
+//! Note that using CRC with AES depends on the used encryption specification, AE-1 or AE-2.
+//! If the file is marked as encrypted with AE-2 the CRC field is ignored, even if it isn't set to 0.
+
+use crate::aes_ctr;
+use crate::types::AesMode;
+use constant_time_eq::constant_time_eq;
+use hmac::{Hmac, Mac};
+use sha1::Sha1;
+use std::io::{self, Read};
+
+/// The length of the password verifcation value in bytes
+const PWD_VERIFY_LENGTH: usize = 2;
+/// The length of the authentication code in bytes
+const AUTH_CODE_LENGTH: usize = 10;
+/// The number of iterations used with PBKDF2
+const ITERATION_COUNT: u32 = 1000;
+
+/// Create a AesCipher depending on the used `AesMode` and the given `key`.
+///
+/// # Panics
+///
+/// This panics if `key` doesn't have the correct size for the chosen aes mode.
+fn cipher_from_mode(aes_mode: AesMode, key: &[u8]) -> Box<dyn aes_ctr::AesCipher> {
+ match aes_mode {
+ AesMode::Aes128 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes128>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ AesMode::Aes192 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes192>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ AesMode::Aes256 => Box::new(aes_ctr::AesCtrZipKeyStream::<aes_ctr::Aes256>::new(key))
+ as Box<dyn aes_ctr::AesCipher>,
+ }
+}
+
+// An aes encrypted file starts with a salt, whose length depends on the used aes mode
+// followed by a 2 byte password verification value
+// then the variable length encrypted data
+// and lastly a 10 byte authentication code
+pub struct AesReader<R> {
+ reader: R,
+ aes_mode: AesMode,
+ data_length: u64,
+}
+
+impl<R: Read> AesReader<R> {
+ pub fn new(reader: R, aes_mode: AesMode, compressed_size: u64) -> AesReader<R> {
+ let data_length = compressed_size
+ - (PWD_VERIFY_LENGTH + AUTH_CODE_LENGTH + aes_mode.salt_length()) as u64;
+
+ Self {
+ reader,
+ aes_mode,
+ data_length,
+ }
+ }
+
+ /// Read the AES header bytes and validate the password.
+ ///
+ /// Even if the validation succeeds, there is still a 1 in 65536 chance that an incorrect
+ /// password was provided.
+ /// It isn't possible to check the authentication code in this step. This will be done after
+ /// reading and decrypting the file.
+ ///
+ /// # Returns
+ ///
+ /// If the password verification failed `Ok(None)` will be returned to match the validate
+ /// method of ZipCryptoReader.
+ pub fn validate(mut self, password: &[u8]) -> io::Result<Option<AesReaderValid<R>>> {
+ let salt_length = self.aes_mode.salt_length();
+ let key_length = self.aes_mode.key_length();
+
+ let mut salt = vec![0; salt_length];
+ self.reader.read_exact(&mut salt)?;
+
+ // next are 2 bytes used for password verification
+ let mut pwd_verification_value = vec![0; PWD_VERIFY_LENGTH];
+ self.reader.read_exact(&mut pwd_verification_value)?;
+
+ // derive a key from the password and salt
+ // the length depends on the aes key length
+ let derived_key_len = 2 * key_length + PWD_VERIFY_LENGTH;
+ let mut derived_key: Vec<u8> = vec![0; derived_key_len];
+
+ // use PBKDF2 with HMAC-Sha1 to derive the key
+ pbkdf2::pbkdf2::<Hmac<Sha1>>(password, &salt, ITERATION_COUNT, &mut derived_key);
+ let decrypt_key = &derived_key[0..key_length];
+ let hmac_key = &derived_key[key_length..key_length * 2];
+ let pwd_verify = &derived_key[derived_key_len - 2..];
+
+ // the last 2 bytes should equal the password verification value
+ if pwd_verification_value != pwd_verify {
+ // wrong password
+ return Ok(None);
+ }
+
+ let cipher = cipher_from_mode(self.aes_mode, decrypt_key);
+ let hmac = Hmac::<Sha1>::new_from_slice(hmac_key).unwrap();
+
+ Ok(Some(AesReaderValid {
+ reader: self.reader,
+ data_remaining: self.data_length,
+ cipher,
+ hmac,
+ finalized: false,
+ }))
+ }
+}
+
+/// A reader for aes encrypted files, which has already passed the first password check.
+///
+/// There is a 1 in 65536 chance that an invalid password passes that check.
+/// After the data has been read and decrypted an HMAC will be checked and provide a final means
+/// to check if either the password is invalid or if the data has been changed.
+pub struct AesReaderValid<R: Read> {
+ reader: R,
+ data_remaining: u64,
+ cipher: Box<dyn aes_ctr::AesCipher>,
+ hmac: Hmac<Sha1>,
+ finalized: bool,
+}
+
+impl<R: Read> Read for AesReaderValid<R> {
+ /// This implementation does not fulfill all requirements set in the trait documentation.
+ ///
+ /// ```txt
+ /// "If an error is returned then it must be guaranteed that no bytes were read."
+ /// ```
+ ///
+ /// Whether this applies to errors that occur while reading the encrypted data depends on the
+ /// underlying reader. If the error occurs while verifying the HMAC, the reader might become
+ /// practically unusable, since its position after the error is not known.
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ if self.data_remaining == 0 {
+ return Ok(0);
+ }
+
+ // get the number of bytes to read, compare as u64 to make sure we can read more than
+ // 2^32 bytes even on 32 bit systems.
+ let bytes_to_read = self.data_remaining.min(buf.len() as u64) as usize;
+ let read = self.reader.read(&mut buf[0..bytes_to_read])?;
+ self.data_remaining -= read as u64;
+
+ // Update the hmac with the encrypted data
+ self.hmac.update(&buf[0..read]);
+
+ // decrypt the data
+ self.cipher.crypt_in_place(&mut buf[0..read]);
+
+ // if there is no data left to read, check the integrity of the data
+ if self.data_remaining == 0 {
+ assert!(
+ !self.finalized,
+ "Tried to use an already finalized HMAC. This is a bug!"
+ );
+ self.finalized = true;
+
+ // Zip uses HMAC-Sha1-80, which only uses the first half of the hash
+ // see https://www.winzip.com/win/en/aes_info.html#auth-faq
+ let mut read_auth_code = [0; AUTH_CODE_LENGTH];
+ self.reader.read_exact(&mut read_auth_code)?;
+ let computed_auth_code = &self.hmac.finalize_reset().into_bytes()[0..AUTH_CODE_LENGTH];
+
+ // use constant time comparison to mitigate timing attacks
+ if !constant_time_eq(computed_auth_code, &read_auth_code) {
+ return Err(
+ io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Invalid authentication code, this could be due to an invalid password or errors in the data"
+ )
+ );
+ }
+ }
+
+ Ok(read)
+ }
+}
+
+impl<R: Read> AesReaderValid<R> {
+ /// Consumes this decoder, returning the underlying reader.
+ pub fn into_inner(self) -> R {
+ self.reader
+ }
+}
diff --git a/third_party/rust/zip/src/aes_ctr.rs b/third_party/rust/zip/src/aes_ctr.rs
new file mode 100644
index 0000000000..0f34335cb4
--- /dev/null
+++ b/third_party/rust/zip/src/aes_ctr.rs
@@ -0,0 +1,281 @@
+//! A counter mode (CTR) for AES to work with the encryption used in zip files.
+//!
+//! This was implemented since the zip specification requires the mode to not use a nonce and uses a
+//! different byte order (little endian) than NIST (big endian).
+//! See [AesCtrZipKeyStream](./struct.AesCtrZipKeyStream.html) for more information.
+
+use aes::cipher::generic_array::GenericArray;
+use aes::{BlockEncrypt, NewBlockCipher};
+use byteorder::WriteBytesExt;
+use std::{any, fmt};
+
+/// Internal block size of an AES cipher.
+const AES_BLOCK_SIZE: usize = 16;
+
+/// AES-128.
+#[derive(Debug)]
+pub struct Aes128;
+/// AES-192
+#[derive(Debug)]
+pub struct Aes192;
+/// AES-256.
+#[derive(Debug)]
+pub struct Aes256;
+
+/// An AES cipher kind.
+pub trait AesKind {
+ /// Key type.
+ type Key: AsRef<[u8]>;
+ /// Cipher used to decrypt.
+ type Cipher;
+}
+
+impl AesKind for Aes128 {
+ type Key = [u8; 16];
+ type Cipher = aes::Aes128;
+}
+
+impl AesKind for Aes192 {
+ type Key = [u8; 24];
+ type Cipher = aes::Aes192;
+}
+
+impl AesKind for Aes256 {
+ type Key = [u8; 32];
+ type Cipher = aes::Aes256;
+}
+
+/// An AES-CTR key stream generator.
+///
+/// Implements the slightly non-standard AES-CTR variant used by WinZip AES encryption.
+///
+/// Typical AES-CTR implementations combine a nonce with a 64 bit counter. WinZIP AES instead uses
+/// no nonce and also uses a different byte order (little endian) than NIST (big endian).
+///
+/// The stream implements the `Read` trait; encryption or decryption is performed by XOR-ing the
+/// bytes from the key stream with the ciphertext/plaintext.
+pub struct AesCtrZipKeyStream<C: AesKind> {
+ /// Current AES counter.
+ counter: u128,
+ /// AES cipher instance.
+ cipher: C::Cipher,
+ /// Stores the currently available keystream bytes.
+ buffer: [u8; AES_BLOCK_SIZE],
+ /// Number of bytes already used up from `buffer`.
+ pos: usize,
+}
+
+impl<C> fmt::Debug for AesCtrZipKeyStream<C>
+where
+ C: AesKind,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "AesCtrZipKeyStream<{}>(counter: {})",
+ any::type_name::<C>(),
+ self.counter
+ )
+ }
+}
+
+impl<C> AesCtrZipKeyStream<C>
+where
+ C: AesKind,
+ C::Cipher: NewBlockCipher,
+{
+ /// Creates a new zip variant AES-CTR key stream.
+ ///
+ /// # Panics
+ ///
+ /// This panics if `key` doesn't have the correct size for cipher `C`.
+ pub fn new(key: &[u8]) -> AesCtrZipKeyStream<C> {
+ AesCtrZipKeyStream {
+ counter: 1,
+ cipher: C::Cipher::new(GenericArray::from_slice(key)),
+ buffer: [0u8; AES_BLOCK_SIZE],
+ pos: AES_BLOCK_SIZE,
+ }
+ }
+}
+
+impl<C> AesCipher for AesCtrZipKeyStream<C>
+where
+ C: AesKind,
+ C::Cipher: BlockEncrypt,
+{
+ /// Decrypt or encrypt `target`.
+ #[inline]
+ fn crypt_in_place(&mut self, mut target: &mut [u8]) {
+ while !target.is_empty() {
+ if self.pos == AES_BLOCK_SIZE {
+ // Note: AES block size is always 16 bytes, same as u128.
+ self.buffer
+ .as_mut()
+ .write_u128::<byteorder::LittleEndian>(self.counter)
+ .expect("did not expect u128 le conversion to fail");
+ self.cipher
+ .encrypt_block(GenericArray::from_mut_slice(&mut self.buffer));
+ self.counter += 1;
+ self.pos = 0;
+ }
+
+ let target_len = target.len().min(AES_BLOCK_SIZE - self.pos);
+
+ xor(
+ &mut target[0..target_len],
+ &self.buffer[self.pos..(self.pos + target_len)],
+ );
+ target = &mut target[target_len..];
+ self.pos += target_len;
+ }
+ }
+}
+
+/// This trait allows using generic AES ciphers with different key sizes.
+pub trait AesCipher {
+ fn crypt_in_place(&mut self, target: &mut [u8]);
+}
+
+/// XORs a slice in place with another slice.
+#[inline]
+fn xor(dest: &mut [u8], src: &[u8]) {
+ assert_eq!(dest.len(), src.len());
+
+ for (lhs, rhs) in dest.iter_mut().zip(src.iter()) {
+ *lhs ^= *rhs;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Aes128, Aes192, Aes256, AesCipher, AesCtrZipKeyStream, AesKind};
+ use aes::{BlockEncrypt, NewBlockCipher};
+
+ /// Checks whether `crypt_in_place` produces the correct plaintext after one use and yields the
+ /// cipertext again after applying it again.
+ fn roundtrip<Aes>(key: &[u8], ciphertext: &mut [u8], expected_plaintext: &[u8])
+ where
+ Aes: AesKind,
+ Aes::Cipher: NewBlockCipher + BlockEncrypt,
+ {
+ let mut key_stream = AesCtrZipKeyStream::<Aes>::new(key);
+
+ let mut plaintext: Vec<u8> = ciphertext.to_vec();
+ key_stream.crypt_in_place(plaintext.as_mut_slice());
+ assert_eq!(plaintext, expected_plaintext.to_vec());
+
+ // Round-tripping should yield the ciphertext again.
+ let mut key_stream = AesCtrZipKeyStream::<Aes>::new(key);
+ key_stream.crypt_in_place(&mut plaintext);
+ assert_eq!(plaintext, ciphertext.to_vec());
+ }
+
+ #[test]
+ #[should_panic]
+ fn new_with_wrong_key_size() {
+ AesCtrZipKeyStream::<Aes128>::new(&[1, 2, 3, 4, 5]);
+ }
+
+ // The data used in these tests was generated with p7zip without any compression.
+ // It's not possible to recreate the exact same data, since a random salt is used for encryption.
+ // `7z a -phelloworld -mem=AES256 -mx=0 aes256_40byte.zip 40byte_data.txt`
+ #[test]
+ fn crypt_aes_256_0_byte() {
+ let mut ciphertext = [];
+ let expected_plaintext = &[];
+ let key = [
+ 0x0b, 0xec, 0x2e, 0xf2, 0x46, 0xf0, 0x7e, 0x35, 0x16, 0x54, 0xe0, 0x98, 0x10, 0xb3,
+ 0x18, 0x55, 0x24, 0xa3, 0x9e, 0x0e, 0x40, 0xe7, 0x92, 0xad, 0xb2, 0x8a, 0x48, 0xf4,
+ 0x5c, 0xd0, 0xc0, 0x54,
+ ];
+
+ roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_128_5_byte() {
+ let mut ciphertext = [0x98, 0xa9, 0x8c, 0x26, 0x0e];
+ let expected_plaintext = b"asdf\n";
+ let key = [
+ 0xe0, 0x25, 0x7b, 0x57, 0x97, 0x6a, 0xa4, 0x23, 0xab, 0x94, 0xaa, 0x44, 0xfd, 0x47,
+ 0x4f, 0xa5,
+ ];
+
+ roundtrip::<Aes128>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_192_5_byte() {
+ let mut ciphertext = [0x36, 0x55, 0x5c, 0x61, 0x3c];
+ let expected_plaintext = b"asdf\n";
+ let key = [
+ 0xe4, 0x4a, 0x88, 0x52, 0x8f, 0xf7, 0x0b, 0x81, 0x7b, 0x75, 0xf1, 0x74, 0x21, 0x37,
+ 0x8c, 0x90, 0xad, 0xbe, 0x4a, 0x65, 0xa8, 0x96, 0x0e, 0xcc,
+ ];
+
+ roundtrip::<Aes192>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_256_5_byte() {
+ let mut ciphertext = [0xc2, 0x47, 0xc0, 0xdc, 0x56];
+ let expected_plaintext = b"asdf\n";
+ let key = [
+ 0x79, 0x5e, 0x17, 0xf2, 0xc6, 0x3d, 0x28, 0x9b, 0x4b, 0x4b, 0xbb, 0xa9, 0xba, 0xc9,
+ 0xa5, 0xee, 0x3a, 0x4f, 0x0f, 0x4b, 0x29, 0xbd, 0xe9, 0xb8, 0x41, 0x9c, 0x41, 0xa5,
+ 0x15, 0xb2, 0x86, 0xab,
+ ];
+
+ roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_128_40_byte() {
+ let mut ciphertext = [
+ 0xcf, 0x72, 0x6b, 0xa1, 0xb2, 0x0f, 0xdf, 0xaa, 0x10, 0xad, 0x9c, 0x7f, 0x6d, 0x1c,
+ 0x8d, 0xb5, 0x16, 0x7e, 0xbb, 0x11, 0x69, 0x52, 0x8c, 0x89, 0x80, 0x32, 0xaa, 0x76,
+ 0xa6, 0x18, 0x31, 0x98, 0xee, 0xdd, 0x22, 0x68, 0xb7, 0xe6, 0x77, 0xd2,
+ ];
+ let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n";
+ let key = [
+ 0x43, 0x2b, 0x6d, 0xbe, 0x05, 0x76, 0x6c, 0x9e, 0xde, 0xca, 0x3b, 0xf8, 0xaf, 0x5d,
+ 0x81, 0xb6,
+ ];
+
+ roundtrip::<Aes128>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_192_40_byte() {
+ let mut ciphertext = [
+ 0xa6, 0xfc, 0x52, 0x79, 0x2c, 0x6c, 0xfe, 0x68, 0xb1, 0xa8, 0xb3, 0x07, 0x52, 0x8b,
+ 0x82, 0xa6, 0x87, 0x9c, 0x72, 0x42, 0x3a, 0xf8, 0xc6, 0xa9, 0xc9, 0xfb, 0x61, 0x19,
+ 0x37, 0xb9, 0x56, 0x62, 0xf4, 0xfc, 0x5e, 0x7a, 0xdd, 0x55, 0x0a, 0x48,
+ ];
+ let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n";
+ let key = [
+ 0xac, 0x92, 0x41, 0xba, 0xde, 0xd9, 0x02, 0xfe, 0x40, 0x92, 0x20, 0xf6, 0x56, 0x03,
+ 0xfe, 0xae, 0x1b, 0xba, 0x01, 0x97, 0x97, 0x79, 0xbb, 0xa6,
+ ];
+
+ roundtrip::<Aes192>(&key, &mut ciphertext, expected_plaintext);
+ }
+
+ #[test]
+ fn crypt_aes_256_40_byte() {
+ let mut ciphertext = [
+ 0xa9, 0x99, 0xbd, 0xea, 0x82, 0x9b, 0x8f, 0x2f, 0xb7, 0x52, 0x2f, 0x6b, 0xd8, 0xf6,
+ 0xab, 0x0e, 0x24, 0x51, 0x9e, 0x18, 0x0f, 0xc0, 0x8f, 0x54, 0x15, 0x80, 0xae, 0xbc,
+ 0xa0, 0x5c, 0x8a, 0x11, 0x8d, 0x14, 0x7e, 0xc5, 0xb4, 0xae, 0xd3, 0x37,
+ ];
+ let expected_plaintext = b"Lorem ipsum dolor sit amet, consectetur\n";
+ let key = [
+ 0x64, 0x7c, 0x7a, 0xde, 0xf0, 0xf2, 0x61, 0x49, 0x1c, 0xf1, 0xf1, 0xe3, 0x37, 0xfc,
+ 0xe1, 0x4d, 0x4a, 0x77, 0xd4, 0xeb, 0x9e, 0x3d, 0x75, 0xce, 0x9a, 0x3e, 0x10, 0x50,
+ 0xc2, 0x07, 0x36, 0xb6,
+ ];
+
+ roundtrip::<Aes256>(&key, &mut ciphertext, expected_plaintext);
+ }
+}
diff --git a/third_party/rust/zip/src/compression.rs b/third_party/rust/zip/src/compression.rs
new file mode 100644
index 0000000000..baec939941
--- /dev/null
+++ b/third_party/rust/zip/src/compression.rs
@@ -0,0 +1,207 @@
+//! Possible ZIP compression methods.
+
+use std::fmt;
+
+#[allow(deprecated)]
+/// Identifies the storage format used to compress a file within a ZIP archive.
+///
+/// Each file's compression method is stored alongside it, allowing the
+/// contents to be read without context.
+///
+/// When creating ZIP files, you may choose the method to use with
+/// [`crate::write::FileOptions::compression_method`]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+#[non_exhaustive]
+pub enum CompressionMethod {
+ /// Store the file as is
+ Stored,
+ /// Compress the file using Deflate
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ Deflated,
+ /// Compress the file using BZIP2
+ #[cfg(feature = "bzip2")]
+ Bzip2,
+ /// Encrypted using AES.
+ ///
+ /// The actual compression method has to be taken from the AES extra data field
+ /// or from `ZipFileData`.
+ #[cfg(feature = "aes-crypto")]
+ Aes,
+ /// Compress the file using ZStandard
+ #[cfg(feature = "zstd")]
+ Zstd,
+ /// Unsupported compression method
+ #[deprecated(since = "0.5.7", note = "use the constants instead")]
+ Unsupported(u16),
+}
+#[allow(deprecated, missing_docs)]
+/// All compression methods defined for the ZIP format
+impl CompressionMethod {
+ pub const STORE: Self = CompressionMethod::Stored;
+ pub const SHRINK: Self = CompressionMethod::Unsupported(1);
+ pub const REDUCE_1: Self = CompressionMethod::Unsupported(2);
+ pub const REDUCE_2: Self = CompressionMethod::Unsupported(3);
+ pub const REDUCE_3: Self = CompressionMethod::Unsupported(4);
+ pub const REDUCE_4: Self = CompressionMethod::Unsupported(5);
+ pub const IMPLODE: Self = CompressionMethod::Unsupported(6);
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ pub const DEFLATE: Self = CompressionMethod::Deflated;
+ #[cfg(not(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ )))]
+ pub const DEFLATE: Self = CompressionMethod::Unsupported(8);
+ pub const DEFLATE64: Self = CompressionMethod::Unsupported(9);
+ pub const PKWARE_IMPLODE: Self = CompressionMethod::Unsupported(10);
+ #[cfg(feature = "bzip2")]
+ pub const BZIP2: Self = CompressionMethod::Bzip2;
+ #[cfg(not(feature = "bzip2"))]
+ pub const BZIP2: Self = CompressionMethod::Unsupported(12);
+ pub const LZMA: Self = CompressionMethod::Unsupported(14);
+ pub const IBM_ZOS_CMPSC: Self = CompressionMethod::Unsupported(16);
+ pub const IBM_TERSE: Self = CompressionMethod::Unsupported(18);
+ pub const ZSTD_DEPRECATED: Self = CompressionMethod::Unsupported(20);
+ #[cfg(feature = "zstd")]
+ pub const ZSTD: Self = CompressionMethod::Zstd;
+ #[cfg(not(feature = "zstd"))]
+ pub const ZSTD: Self = CompressionMethod::Unsupported(93);
+ pub const MP3: Self = CompressionMethod::Unsupported(94);
+ pub const XZ: Self = CompressionMethod::Unsupported(95);
+ pub const JPEG: Self = CompressionMethod::Unsupported(96);
+ pub const WAVPACK: Self = CompressionMethod::Unsupported(97);
+ pub const PPMD: Self = CompressionMethod::Unsupported(98);
+ #[cfg(feature = "aes-crypto")]
+ pub const AES: Self = CompressionMethod::Aes;
+ #[cfg(not(feature = "aes-crypto"))]
+ pub const AES: Self = CompressionMethod::Unsupported(99);
+}
+impl CompressionMethod {
+ /// Converts an u16 to its corresponding CompressionMethod
+ #[deprecated(
+ since = "0.5.7",
+ note = "use a constant to construct a compression method"
+ )]
+ pub fn from_u16(val: u16) -> CompressionMethod {
+ #[allow(deprecated)]
+ match val {
+ 0 => CompressionMethod::Stored,
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ 8 => CompressionMethod::Deflated,
+ #[cfg(feature = "bzip2")]
+ 12 => CompressionMethod::Bzip2,
+ #[cfg(feature = "zstd")]
+ 93 => CompressionMethod::Zstd,
+ #[cfg(feature = "aes-crypto")]
+ 99 => CompressionMethod::Aes,
+
+ v => CompressionMethod::Unsupported(v),
+ }
+ }
+
+ /// Converts a CompressionMethod to a u16
+ #[deprecated(
+ since = "0.5.7",
+ note = "to match on other compression methods, use a constant"
+ )]
+ pub fn to_u16(self) -> u16 {
+ #[allow(deprecated)]
+ match self {
+ CompressionMethod::Stored => 0,
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ CompressionMethod::Deflated => 8,
+ #[cfg(feature = "bzip2")]
+ CompressionMethod::Bzip2 => 12,
+ #[cfg(feature = "aes-crypto")]
+ CompressionMethod::Aes => 99,
+ #[cfg(feature = "zstd")]
+ CompressionMethod::Zstd => 93,
+
+ CompressionMethod::Unsupported(v) => v,
+ }
+ }
+}
+
+impl fmt::Display for CompressionMethod {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Just duplicate what the Debug format looks like, i.e, the enum key:
+ write!(f, "{self:?}")
+ }
+}
+
+/// The compression methods which have been implemented.
+pub const SUPPORTED_COMPRESSION_METHODS: &[CompressionMethod] = &[
+ CompressionMethod::Stored,
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ CompressionMethod::Deflated,
+ #[cfg(feature = "bzip2")]
+ CompressionMethod::Bzip2,
+ #[cfg(feature = "zstd")]
+ CompressionMethod::Zstd,
+];
+
+#[cfg(test)]
+mod test {
+ use super::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
+
+ #[test]
+ fn from_eq_to() {
+ for v in 0..(u16::MAX as u32 + 1) {
+ #[allow(deprecated)]
+ let from = CompressionMethod::from_u16(v as u16);
+ #[allow(deprecated)]
+ let to = from.to_u16() as u32;
+ assert_eq!(v, to);
+ }
+ }
+
+ #[test]
+ fn to_eq_from() {
+ fn check_match(method: CompressionMethod) {
+ #[allow(deprecated)]
+ let to = method.to_u16();
+ #[allow(deprecated)]
+ let from = CompressionMethod::from_u16(to);
+ #[allow(deprecated)]
+ let back = from.to_u16();
+ assert_eq!(to, back);
+ }
+
+ for &method in SUPPORTED_COMPRESSION_METHODS {
+ check_match(method);
+ }
+ }
+
+ #[test]
+ fn to_display_fmt() {
+ fn check_match(method: CompressionMethod) {
+ let debug_str = format!("{method:?}");
+ let display_str = format!("{method}");
+ assert_eq!(debug_str, display_str);
+ }
+
+ for &method in SUPPORTED_COMPRESSION_METHODS {
+ check_match(method);
+ }
+ }
+}
diff --git a/third_party/rust/zip/src/cp437.rs b/third_party/rust/zip/src/cp437.rs
new file mode 100644
index 0000000000..4dba9af12f
--- /dev/null
+++ b/third_party/rust/zip/src/cp437.rs
@@ -0,0 +1,204 @@
+//! Convert a string in IBM codepage 437 to UTF-8
+
+/// Trait to convert IBM codepage 437 to the target type
+pub trait FromCp437 {
+ /// Target type
+ type Target;
+
+ /// Function that does the conversion from cp437.
+ /// Generally allocations will be avoided if all data falls into the ASCII range.
+ #[allow(clippy::wrong_self_convention)]
+ fn from_cp437(self) -> Self::Target;
+}
+
+impl<'a> FromCp437 for &'a [u8] {
+ type Target = ::std::borrow::Cow<'a, str>;
+
+ fn from_cp437(self) -> Self::Target {
+ if self.iter().all(|c| *c < 0x80) {
+ ::std::str::from_utf8(self).unwrap().into()
+ } else {
+ self.iter().map(|c| to_char(*c)).collect::<String>().into()
+ }
+ }
+}
+
+impl FromCp437 for Vec<u8> {
+ type Target = String;
+
+ fn from_cp437(self) -> Self::Target {
+ if self.iter().all(|c| *c < 0x80) {
+ String::from_utf8(self).unwrap()
+ } else {
+ self.into_iter().map(to_char).collect()
+ }
+ }
+}
+
+fn to_char(input: u8) -> char {
+ let output = match input {
+ 0x00..=0x7f => input as u32,
+ 0x80 => 0x00c7,
+ 0x81 => 0x00fc,
+ 0x82 => 0x00e9,
+ 0x83 => 0x00e2,
+ 0x84 => 0x00e4,
+ 0x85 => 0x00e0,
+ 0x86 => 0x00e5,
+ 0x87 => 0x00e7,
+ 0x88 => 0x00ea,
+ 0x89 => 0x00eb,
+ 0x8a => 0x00e8,
+ 0x8b => 0x00ef,
+ 0x8c => 0x00ee,
+ 0x8d => 0x00ec,
+ 0x8e => 0x00c4,
+ 0x8f => 0x00c5,
+ 0x90 => 0x00c9,
+ 0x91 => 0x00e6,
+ 0x92 => 0x00c6,
+ 0x93 => 0x00f4,
+ 0x94 => 0x00f6,
+ 0x95 => 0x00f2,
+ 0x96 => 0x00fb,
+ 0x97 => 0x00f9,
+ 0x98 => 0x00ff,
+ 0x99 => 0x00d6,
+ 0x9a => 0x00dc,
+ 0x9b => 0x00a2,
+ 0x9c => 0x00a3,
+ 0x9d => 0x00a5,
+ 0x9e => 0x20a7,
+ 0x9f => 0x0192,
+ 0xa0 => 0x00e1,
+ 0xa1 => 0x00ed,
+ 0xa2 => 0x00f3,
+ 0xa3 => 0x00fa,
+ 0xa4 => 0x00f1,
+ 0xa5 => 0x00d1,
+ 0xa6 => 0x00aa,
+ 0xa7 => 0x00ba,
+ 0xa8 => 0x00bf,
+ 0xa9 => 0x2310,
+ 0xaa => 0x00ac,
+ 0xab => 0x00bd,
+ 0xac => 0x00bc,
+ 0xad => 0x00a1,
+ 0xae => 0x00ab,
+ 0xaf => 0x00bb,
+ 0xb0 => 0x2591,
+ 0xb1 => 0x2592,
+ 0xb2 => 0x2593,
+ 0xb3 => 0x2502,
+ 0xb4 => 0x2524,
+ 0xb5 => 0x2561,
+ 0xb6 => 0x2562,
+ 0xb7 => 0x2556,
+ 0xb8 => 0x2555,
+ 0xb9 => 0x2563,
+ 0xba => 0x2551,
+ 0xbb => 0x2557,
+ 0xbc => 0x255d,
+ 0xbd => 0x255c,
+ 0xbe => 0x255b,
+ 0xbf => 0x2510,
+ 0xc0 => 0x2514,
+ 0xc1 => 0x2534,
+ 0xc2 => 0x252c,
+ 0xc3 => 0x251c,
+ 0xc4 => 0x2500,
+ 0xc5 => 0x253c,
+ 0xc6 => 0x255e,
+ 0xc7 => 0x255f,
+ 0xc8 => 0x255a,
+ 0xc9 => 0x2554,
+ 0xca => 0x2569,
+ 0xcb => 0x2566,
+ 0xcc => 0x2560,
+ 0xcd => 0x2550,
+ 0xce => 0x256c,
+ 0xcf => 0x2567,
+ 0xd0 => 0x2568,
+ 0xd1 => 0x2564,
+ 0xd2 => 0x2565,
+ 0xd3 => 0x2559,
+ 0xd4 => 0x2558,
+ 0xd5 => 0x2552,
+ 0xd6 => 0x2553,
+ 0xd7 => 0x256b,
+ 0xd8 => 0x256a,
+ 0xd9 => 0x2518,
+ 0xda => 0x250c,
+ 0xdb => 0x2588,
+ 0xdc => 0x2584,
+ 0xdd => 0x258c,
+ 0xde => 0x2590,
+ 0xdf => 0x2580,
+ 0xe0 => 0x03b1,
+ 0xe1 => 0x00df,
+ 0xe2 => 0x0393,
+ 0xe3 => 0x03c0,
+ 0xe4 => 0x03a3,
+ 0xe5 => 0x03c3,
+ 0xe6 => 0x00b5,
+ 0xe7 => 0x03c4,
+ 0xe8 => 0x03a6,
+ 0xe9 => 0x0398,
+ 0xea => 0x03a9,
+ 0xeb => 0x03b4,
+ 0xec => 0x221e,
+ 0xed => 0x03c6,
+ 0xee => 0x03b5,
+ 0xef => 0x2229,
+ 0xf0 => 0x2261,
+ 0xf1 => 0x00b1,
+ 0xf2 => 0x2265,
+ 0xf3 => 0x2264,
+ 0xf4 => 0x2320,
+ 0xf5 => 0x2321,
+ 0xf6 => 0x00f7,
+ 0xf7 => 0x2248,
+ 0xf8 => 0x00b0,
+ 0xf9 => 0x2219,
+ 0xfa => 0x00b7,
+ 0xfb => 0x221a,
+ 0xfc => 0x207f,
+ 0xfd => 0x00b2,
+ 0xfe => 0x25a0,
+ 0xff => 0x00a0,
+ };
+ ::std::char::from_u32(output).unwrap()
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn to_char_valid() {
+ for i in 0x00_u32..0x100 {
+ super::to_char(i as u8);
+ }
+ }
+
+ #[test]
+ fn ascii() {
+ for i in 0x00..0x80 {
+ assert_eq!(super::to_char(i), i as char);
+ }
+ }
+
+ #[test]
+ fn example_slice() {
+ use super::FromCp437;
+ let data = b"Cura\x87ao";
+ assert!(::std::str::from_utf8(data).is_err());
+ assert_eq!(data.from_cp437(), "Curaçao");
+ }
+
+ #[test]
+ fn example_vec() {
+ use super::FromCp437;
+ let data = vec![0xCC, 0xCD, 0xCD, 0xB9];
+ assert!(String::from_utf8(data.clone()).is_err());
+ assert_eq!(&data.from_cp437(), "╠══╣");
+ }
+}
diff --git a/third_party/rust/zip/src/crc32.rs b/third_party/rust/zip/src/crc32.rs
new file mode 100644
index 0000000000..ebace898dc
--- /dev/null
+++ b/third_party/rust/zip/src/crc32.rs
@@ -0,0 +1,100 @@
+//! Helper module to compute a CRC32 checksum
+
+use std::io;
+use std::io::prelude::*;
+
+use crc32fast::Hasher;
+
+/// Reader that validates the CRC32 when it reaches the EOF.
+pub struct Crc32Reader<R> {
+ inner: R,
+ hasher: Hasher,
+ check: u32,
+ /// Signals if `inner` stores aes encrypted data.
+ /// AE-2 encrypted data doesn't use crc and sets the value to 0.
+ ae2_encrypted: bool,
+}
+
+impl<R> Crc32Reader<R> {
+ /// Get a new Crc32Reader which checks the inner reader against checksum.
+ /// The check is disabled if `ae2_encrypted == true`.
+ pub(crate) fn new(inner: R, checksum: u32, ae2_encrypted: bool) -> Crc32Reader<R> {
+ Crc32Reader {
+ inner,
+ hasher: Hasher::new(),
+ check: checksum,
+ ae2_encrypted,
+ }
+ }
+
+ fn check_matches(&self) -> bool {
+ self.check == self.hasher.clone().finalize()
+ }
+
+ pub fn into_inner(self) -> R {
+ self.inner
+ }
+}
+
+impl<R: Read> Read for Crc32Reader<R> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let invalid_check = !buf.is_empty() && !self.check_matches() && !self.ae2_encrypted;
+
+ let count = match self.inner.read(buf) {
+ Ok(0) if invalid_check => {
+ return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum"))
+ }
+ Ok(n) => n,
+ Err(e) => return Err(e),
+ };
+ self.hasher.update(&buf[0..count]);
+ Ok(count)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io::Read;
+
+ #[test]
+ fn test_empty_reader() {
+ let data: &[u8] = b"";
+ let mut buf = [0; 1];
+
+ let mut reader = Crc32Reader::new(data, 0, false);
+ assert_eq!(reader.read(&mut buf).unwrap(), 0);
+
+ let mut reader = Crc32Reader::new(data, 1, false);
+ assert!(reader
+ .read(&mut buf)
+ .unwrap_err()
+ .to_string()
+ .contains("Invalid checksum"));
+ }
+
+ #[test]
+ fn test_byte_by_byte() {
+ let data: &[u8] = b"1234";
+ let mut buf = [0; 1];
+
+ let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false);
+ assert_eq!(reader.read(&mut buf).unwrap(), 1);
+ assert_eq!(reader.read(&mut buf).unwrap(), 1);
+ assert_eq!(reader.read(&mut buf).unwrap(), 1);
+ assert_eq!(reader.read(&mut buf).unwrap(), 1);
+ assert_eq!(reader.read(&mut buf).unwrap(), 0);
+ // Can keep reading 0 bytes after the end
+ assert_eq!(reader.read(&mut buf).unwrap(), 0);
+ }
+
+ #[test]
+ fn test_zero_read() {
+ let data: &[u8] = b"1234";
+ let mut buf = [0; 5];
+
+ let mut reader = Crc32Reader::new(data, 0x9be3e0a3, false);
+ assert_eq!(reader.read(&mut buf[..0]).unwrap(), 0);
+ assert_eq!(reader.read(&mut buf).unwrap(), 4);
+ }
+}
diff --git a/third_party/rust/zip/src/lib.rs b/third_party/rust/zip/src/lib.rs
new file mode 100644
index 0000000000..0fee99cc8d
--- /dev/null
+++ b/third_party/rust/zip/src/lib.rs
@@ -0,0 +1,44 @@
+//! A library for reading and writing ZIP archives.
+//! ZIP is a format designed for cross-platform file "archiving".
+//! That is, storing a collection of files in a single datastream
+//! to make them easier to share between computers.
+//! Additionally, ZIP is able to compress and encrypt files in its
+//! archives.
+//!
+//! The current implementation is based on [PKWARE's APPNOTE.TXT v6.3.9](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
+//!
+//! ---
+//!
+//! [`zip`](`crate`) has support for the most common ZIP archives found in common use.
+//! However, in special cases,
+//! there are some zip archives that are difficult to read or write.
+//!
+//! This is a list of supported features:
+//!
+//! | | Reading | Writing |
+//! | ------- | ------ | ------- |
+//! | Deflate | ✅ [->](`crate::ZipArchive::by_name`) | ✅ [->](`crate::write::FileOptions::compression_method`) |
+//!
+//!
+//!
+
+#![warn(missing_docs)]
+
+pub use crate::compression::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
+pub use crate::read::ZipArchive;
+pub use crate::types::DateTime;
+pub use crate::write::ZipWriter;
+
+#[cfg(feature = "aes-crypto")]
+mod aes;
+#[cfg(feature = "aes-crypto")]
+mod aes_ctr;
+mod compression;
+mod cp437;
+mod crc32;
+pub mod read;
+pub mod result;
+mod spec;
+mod types;
+pub mod write;
+mod zipcrypto;
diff --git a/third_party/rust/zip/src/read.rs b/third_party/rust/zip/src/read.rs
new file mode 100644
index 0000000000..dad20c260b
--- /dev/null
+++ b/third_party/rust/zip/src/read.rs
@@ -0,0 +1,1312 @@
+//! Types for reading ZIP archives
+
+#[cfg(feature = "aes-crypto")]
+use crate::aes::{AesReader, AesReaderValid};
+use crate::compression::CompressionMethod;
+use crate::cp437::FromCp437;
+use crate::crc32::Crc32Reader;
+use crate::result::{InvalidPassword, ZipError, ZipResult};
+use crate::spec;
+use crate::types::{AesMode, AesVendorVersion, AtomicU64, DateTime, System, ZipFileData};
+use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
+use byteorder::{LittleEndian, ReadBytesExt};
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::io::{self, prelude::*};
+use std::path::{Component, Path};
+use std::sync::Arc;
+
+#[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+))]
+use flate2::read::DeflateDecoder;
+
+#[cfg(feature = "bzip2")]
+use bzip2::read::BzDecoder;
+
+#[cfg(feature = "zstd")]
+use zstd::stream::read::Decoder as ZstdDecoder;
+
+mod ffi {
+ pub const S_IFDIR: u32 = 0o0040000;
+ pub const S_IFREG: u32 = 0o0100000;
+}
+
+// Put the struct declaration in a private module to convince rustdoc to display ZipArchive nicely
+pub(crate) mod zip_archive {
+ /// Extract immutable data from `ZipArchive` to make it cheap to clone
+ #[derive(Debug)]
+ pub(crate) struct Shared {
+ pub(super) files: Vec<super::ZipFileData>,
+ pub(super) names_map: super::HashMap<String, usize>,
+ pub(super) offset: u64,
+ pub(super) comment: Vec<u8>,
+ }
+
+ /// ZIP archive reader
+ ///
+ /// At the moment, this type is cheap to clone if this is the case for the
+ /// reader it uses. However, this is not guaranteed by this crate and it may
+ /// change in the future.
+ ///
+ /// ```no_run
+ /// use std::io::prelude::*;
+ /// fn list_zip_contents(reader: impl Read + Seek) -> zip::result::ZipResult<()> {
+ /// let mut zip = zip::ZipArchive::new(reader)?;
+ ///
+ /// for i in 0..zip.len() {
+ /// let mut file = zip.by_index(i)?;
+ /// println!("Filename: {}", file.name());
+ /// std::io::copy(&mut file, &mut std::io::stdout());
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[derive(Clone, Debug)]
+ pub struct ZipArchive<R> {
+ pub(super) reader: R,
+ pub(super) shared: super::Arc<Shared>,
+ }
+}
+
+pub use zip_archive::ZipArchive;
+#[allow(clippy::large_enum_variant)]
+enum CryptoReader<'a> {
+ Plaintext(io::Take<&'a mut dyn Read>),
+ ZipCrypto(ZipCryptoReaderValid<io::Take<&'a mut dyn Read>>),
+ #[cfg(feature = "aes-crypto")]
+ Aes {
+ reader: AesReaderValid<io::Take<&'a mut dyn Read>>,
+ vendor_version: AesVendorVersion,
+ },
+}
+
+impl<'a> Read for CryptoReader<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ match self {
+ CryptoReader::Plaintext(r) => r.read(buf),
+ CryptoReader::ZipCrypto(r) => r.read(buf),
+ #[cfg(feature = "aes-crypto")]
+ CryptoReader::Aes { reader: r, .. } => r.read(buf),
+ }
+ }
+}
+
+impl<'a> CryptoReader<'a> {
+ /// Consumes this decoder, returning the underlying reader.
+ pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
+ match self {
+ CryptoReader::Plaintext(r) => r,
+ CryptoReader::ZipCrypto(r) => r.into_inner(),
+ #[cfg(feature = "aes-crypto")]
+ CryptoReader::Aes { reader: r, .. } => r.into_inner(),
+ }
+ }
+
+ /// Returns `true` if the data is encrypted using AE2.
+ pub fn is_ae2_encrypted(&self) -> bool {
+ #[cfg(feature = "aes-crypto")]
+ return matches!(
+ self,
+ CryptoReader::Aes {
+ vendor_version: AesVendorVersion::Ae2,
+ ..
+ }
+ );
+ #[cfg(not(feature = "aes-crypto"))]
+ false
+ }
+}
+
+enum ZipFileReader<'a> {
+ NoReader,
+ Raw(io::Take<&'a mut dyn io::Read>),
+ Stored(Crc32Reader<CryptoReader<'a>>),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ Deflated(Crc32Reader<flate2::read::DeflateDecoder<CryptoReader<'a>>>),
+ #[cfg(feature = "bzip2")]
+ Bzip2(Crc32Reader<BzDecoder<CryptoReader<'a>>>),
+ #[cfg(feature = "zstd")]
+ Zstd(Crc32Reader<ZstdDecoder<'a, io::BufReader<CryptoReader<'a>>>>),
+}
+
+impl<'a> Read for ZipFileReader<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ match self {
+ ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
+ ZipFileReader::Raw(r) => r.read(buf),
+ ZipFileReader::Stored(r) => r.read(buf),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ ZipFileReader::Deflated(r) => r.read(buf),
+ #[cfg(feature = "bzip2")]
+ ZipFileReader::Bzip2(r) => r.read(buf),
+ #[cfg(feature = "zstd")]
+ ZipFileReader::Zstd(r) => r.read(buf),
+ }
+ }
+}
+
+impl<'a> ZipFileReader<'a> {
+ /// Consumes this decoder, returning the underlying reader.
+ pub fn into_inner(self) -> io::Take<&'a mut dyn Read> {
+ match self {
+ ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"),
+ ZipFileReader::Raw(r) => r,
+ ZipFileReader::Stored(r) => r.into_inner().into_inner(),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ ZipFileReader::Deflated(r) => r.into_inner().into_inner().into_inner(),
+ #[cfg(feature = "bzip2")]
+ ZipFileReader::Bzip2(r) => r.into_inner().into_inner().into_inner(),
+ #[cfg(feature = "zstd")]
+ ZipFileReader::Zstd(r) => r.into_inner().finish().into_inner().into_inner(),
+ }
+ }
+}
+
+/// A struct for reading a zip file
+pub struct ZipFile<'a> {
+ data: Cow<'a, ZipFileData>,
+ crypto_reader: Option<CryptoReader<'a>>,
+ reader: ZipFileReader<'a>,
+}
+
+fn find_content<'a>(
+ data: &ZipFileData,
+ reader: &'a mut (impl Read + Seek),
+) -> ZipResult<io::Take<&'a mut dyn Read>> {
+ // Parse local header
+ reader.seek(io::SeekFrom::Start(data.header_start))?;
+ let signature = reader.read_u32::<LittleEndian>()?;
+ if signature != spec::LOCAL_FILE_HEADER_SIGNATURE {
+ return Err(ZipError::InvalidArchive("Invalid local file header"));
+ }
+
+ reader.seek(io::SeekFrom::Current(22))?;
+ let file_name_length = reader.read_u16::<LittleEndian>()? as u64;
+ let extra_field_length = reader.read_u16::<LittleEndian>()? as u64;
+ let magic_and_header = 4 + 22 + 2 + 2;
+ let data_start = data.header_start + magic_and_header + file_name_length + extra_field_length;
+ data.data_start.store(data_start);
+
+ reader.seek(io::SeekFrom::Start(data_start))?;
+ Ok((reader as &mut dyn Read).take(data.compressed_size))
+}
+
+#[allow(clippy::too_many_arguments)]
+fn make_crypto_reader<'a>(
+ compression_method: crate::compression::CompressionMethod,
+ crc32: u32,
+ last_modified_time: DateTime,
+ using_data_descriptor: bool,
+ reader: io::Take<&'a mut dyn io::Read>,
+ password: Option<&[u8]>,
+ aes_info: Option<(AesMode, AesVendorVersion)>,
+ #[cfg(feature = "aes-crypto")] compressed_size: u64,
+) -> ZipResult<Result<CryptoReader<'a>, InvalidPassword>> {
+ #[allow(deprecated)]
+ {
+ if let CompressionMethod::Unsupported(_) = compression_method {
+ return unsupported_zip_error("Compression method not supported");
+ }
+ }
+
+ let reader = match (password, aes_info) {
+ #[cfg(not(feature = "aes-crypto"))]
+ (Some(_), Some(_)) => {
+ return Err(ZipError::UnsupportedArchive(
+ "AES encrypted files cannot be decrypted without the aes-crypto feature.",
+ ))
+ }
+ #[cfg(feature = "aes-crypto")]
+ (Some(password), Some((aes_mode, vendor_version))) => {
+ match AesReader::new(reader, aes_mode, compressed_size).validate(password)? {
+ None => return Ok(Err(InvalidPassword)),
+ Some(r) => CryptoReader::Aes {
+ reader: r,
+ vendor_version,
+ },
+ }
+ }
+ (Some(password), None) => {
+ let validator = if using_data_descriptor {
+ ZipCryptoValidator::InfoZipMsdosTime(last_modified_time.timepart())
+ } else {
+ ZipCryptoValidator::PkzipCrc32(crc32)
+ };
+ match ZipCryptoReader::new(reader, password).validate(validator)? {
+ None => return Ok(Err(InvalidPassword)),
+ Some(r) => CryptoReader::ZipCrypto(r),
+ }
+ }
+ (None, Some(_)) => return Ok(Err(InvalidPassword)),
+ (None, None) => CryptoReader::Plaintext(reader),
+ };
+ Ok(Ok(reader))
+}
+
+fn make_reader(
+ compression_method: CompressionMethod,
+ crc32: u32,
+ reader: CryptoReader,
+) -> ZipFileReader {
+ let ae2_encrypted = reader.is_ae2_encrypted();
+
+ match compression_method {
+ CompressionMethod::Stored => {
+ ZipFileReader::Stored(Crc32Reader::new(reader, crc32, ae2_encrypted))
+ }
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ CompressionMethod::Deflated => {
+ let deflate_reader = DeflateDecoder::new(reader);
+ ZipFileReader::Deflated(Crc32Reader::new(deflate_reader, crc32, ae2_encrypted))
+ }
+ #[cfg(feature = "bzip2")]
+ CompressionMethod::Bzip2 => {
+ let bzip2_reader = BzDecoder::new(reader);
+ ZipFileReader::Bzip2(Crc32Reader::new(bzip2_reader, crc32, ae2_encrypted))
+ }
+ #[cfg(feature = "zstd")]
+ CompressionMethod::Zstd => {
+ let zstd_reader = ZstdDecoder::new(reader).unwrap();
+ ZipFileReader::Zstd(Crc32Reader::new(zstd_reader, crc32, ae2_encrypted))
+ }
+ _ => panic!("Compression method not supported"),
+ }
+}
+
+impl<R: Read + io::Seek> ZipArchive<R> {
+ /// Get the directory start offset and number of files. This is done in a
+ /// separate function to ease the control flow design.
+ pub(crate) fn get_directory_counts(
+ reader: &mut R,
+ footer: &spec::CentralDirectoryEnd,
+ cde_start_pos: u64,
+ ) -> ZipResult<(u64, u64, usize)> {
+ // See if there's a ZIP64 footer. The ZIP64 locator if present will
+ // have its signature 20 bytes in front of the standard footer. The
+ // standard footer, in turn, is 22+N bytes large, where N is the
+ // comment length. Therefore:
+ let zip64locator = if reader
+ .seek(io::SeekFrom::End(
+ -(20 + 22 + footer.zip_file_comment.len() as i64),
+ ))
+ .is_ok()
+ {
+ match spec::Zip64CentralDirectoryEndLocator::parse(reader) {
+ Ok(loc) => Some(loc),
+ Err(ZipError::InvalidArchive(_)) => {
+ // No ZIP64 header; that's actually fine. We're done here.
+ None
+ }
+ Err(e) => {
+ // Yikes, a real problem
+ return Err(e);
+ }
+ }
+ } else {
+ // Empty Zip files will have nothing else so this error might be fine. If
+ // not, we'll find out soon.
+ None
+ };
+
+ match zip64locator {
+ None => {
+ // Some zip files have data prepended to them, resulting in the
+ // offsets all being too small. Get the amount of error by comparing
+ // the actual file position we found the CDE at with the offset
+ // recorded in the CDE.
+ let archive_offset = cde_start_pos
+ .checked_sub(footer.central_directory_size as u64)
+ .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
+ .ok_or(ZipError::InvalidArchive(
+ "Invalid central directory size or offset",
+ ))?;
+
+ let directory_start = footer.central_directory_offset as u64 + archive_offset;
+ let number_of_files = footer.number_of_files_on_this_disk as usize;
+ Ok((archive_offset, directory_start, number_of_files))
+ }
+ Some(locator64) => {
+ // If we got here, this is indeed a ZIP64 file.
+
+ if !footer.record_too_small()
+ && footer.disk_number as u32 != locator64.disk_with_central_directory
+ {
+ return unsupported_zip_error(
+ "Support for multi-disk files is not implemented",
+ );
+ }
+
+ // We need to reassess `archive_offset`. We know where the ZIP64
+ // central-directory-end structure *should* be, but unfortunately we
+ // don't know how to precisely relate that location to our current
+ // actual offset in the file, since there may be junk at its
+ // beginning. Therefore we need to perform another search, as in
+ // read::CentralDirectoryEnd::find_and_parse, except now we search
+ // forward.
+
+ let search_upper_bound = cde_start_pos
+ .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator
+ .ok_or(ZipError::InvalidArchive(
+ "File cannot contain ZIP64 central directory end",
+ ))?;
+ let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse(
+ reader,
+ locator64.end_of_central_directory_offset,
+ search_upper_bound,
+ )?;
+
+ if footer.disk_number != footer.disk_with_central_directory {
+ return unsupported_zip_error(
+ "Support for multi-disk files is not implemented",
+ );
+ }
+
+ let directory_start = footer
+ .central_directory_offset
+ .checked_add(archive_offset)
+ .ok_or({
+ ZipError::InvalidArchive("Invalid central directory size or offset")
+ })?;
+
+ Ok((
+ archive_offset,
+ directory_start,
+ footer.number_of_files as usize,
+ ))
+ }
+ }
+ }
+
+ /// Read a ZIP archive, collecting the files it contains
+ ///
+ /// This uses the central directory record of the ZIP file, and ignores local file headers
+ pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
+ let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
+
+ if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory {
+ return unsupported_zip_error("Support for multi-disk files is not implemented");
+ }
+
+ let (archive_offset, directory_start, number_of_files) =
+ Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
+
+ // If the parsed number of files is greater than the offset then
+ // something fishy is going on and we shouldn't trust number_of_files.
+ let file_capacity = if number_of_files > cde_start_pos as usize {
+ 0
+ } else {
+ number_of_files
+ };
+
+ let mut files = Vec::with_capacity(file_capacity);
+ let mut names_map = HashMap::with_capacity(file_capacity);
+
+ if reader.seek(io::SeekFrom::Start(directory_start)).is_err() {
+ return Err(ZipError::InvalidArchive(
+ "Could not seek to start of central directory",
+ ));
+ }
+
+ for _ in 0..number_of_files {
+ let file = central_header_to_zip_file(&mut reader, archive_offset)?;
+ names_map.insert(file.file_name.clone(), files.len());
+ files.push(file);
+ }
+
+ let shared = Arc::new(zip_archive::Shared {
+ files,
+ names_map,
+ offset: archive_offset,
+ comment: footer.zip_file_comment,
+ });
+
+ Ok(ZipArchive { reader, shared })
+ }
+ /// Extract a Zip archive into a directory, overwriting files if they
+ /// already exist. Paths are sanitized with [`ZipFile::enclosed_name`].
+ ///
+ /// Extraction is not atomic; If an error is encountered, some of the files
+ /// may be left on disk.
+ pub fn extract<P: AsRef<Path>>(&mut self, directory: P) -> ZipResult<()> {
+ use std::fs;
+
+ for i in 0..self.len() {
+ let mut file = self.by_index(i)?;
+ let filepath = file
+ .enclosed_name()
+ .ok_or(ZipError::InvalidArchive("Invalid file path"))?;
+
+ let outpath = directory.as_ref().join(filepath);
+
+ if file.name().ends_with('/') {
+ fs::create_dir_all(&outpath)?;
+ } else {
+ if let Some(p) = outpath.parent() {
+ if !p.exists() {
+ fs::create_dir_all(p)?;
+ }
+ }
+ let mut outfile = fs::File::create(&outpath)?;
+ io::copy(&mut file, &mut outfile)?;
+ }
+ // Get and Set permissions
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ if let Some(mode) = file.unix_mode() {
+ fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Number of files contained in this zip.
+ pub fn len(&self) -> usize {
+ self.shared.files.len()
+ }
+
+ /// Whether this zip archive contains no files
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes.
+ ///
+ /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size
+ /// of that prepended data.
+ pub fn offset(&self) -> u64 {
+ self.shared.offset
+ }
+
+ /// Get the comment of the zip archive.
+ pub fn comment(&self) -> &[u8] {
+ &self.shared.comment
+ }
+
+ /// Returns an iterator over all the file and directory names in this archive.
+ pub fn file_names(&self) -> impl Iterator<Item = &str> {
+ self.shared.names_map.keys().map(|s| s.as_str())
+ }
+
+ /// Search for a file entry by name, decrypt with given password
+ ///
+ /// # Warning
+ ///
+ /// The implementation of the cryptographic algorithms has not
+ /// gone through a correctness review, and you should assume it is insecure:
+ /// passwords used with this API may be compromised.
+ ///
+ /// This function sometimes accepts wrong password. This is because the ZIP spec only allows us
+ /// to check for a 1/256 chance that the password is correct.
+ /// There are many passwords out there that will also pass the validity checks
+ /// we are able to perform. This is a weakness of the ZipCrypto algorithm,
+ /// due to its fairly primitive approach to cryptography.
+ pub fn by_name_decrypt<'a>(
+ &'a mut self,
+ name: &str,
+ password: &[u8],
+ ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
+ self.by_name_with_optional_password(name, Some(password))
+ }
+
+ /// Search for a file entry by name
+ pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
+ Ok(self.by_name_with_optional_password(name, None)?.unwrap())
+ }
+
+ fn by_name_with_optional_password<'a>(
+ &'a mut self,
+ name: &str,
+ password: Option<&[u8]>,
+ ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
+ let index = match self.shared.names_map.get(name) {
+ Some(index) => *index,
+ None => {
+ return Err(ZipError::FileNotFound);
+ }
+ };
+ self.by_index_with_optional_password(index, password)
+ }
+
+ /// Get a contained file by index, decrypt with given password
+ ///
+ /// # Warning
+ ///
+ /// The implementation of the cryptographic algorithms has not
+ /// gone through a correctness review, and you should assume it is insecure:
+ /// passwords used with this API may be compromised.
+ ///
+ /// This function sometimes accepts wrong password. This is because the ZIP spec only allows us
+ /// to check for a 1/256 chance that the password is correct.
+ /// There are many passwords out there that will also pass the validity checks
+ /// we are able to perform. This is a weakness of the ZipCrypto algorithm,
+ /// due to its fairly primitive approach to cryptography.
+ pub fn by_index_decrypt<'a>(
+ &'a mut self,
+ file_number: usize,
+ password: &[u8],
+ ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
+ self.by_index_with_optional_password(file_number, Some(password))
+ }
+
+ /// Get a contained file by index
+ pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
+ Ok(self
+ .by_index_with_optional_password(file_number, None)?
+ .unwrap())
+ }
+
+ /// Get a contained file by index without decompressing it
+ pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_>> {
+ let reader = &mut self.reader;
+ self.shared
+ .files
+ .get(file_number)
+ .ok_or(ZipError::FileNotFound)
+ .and_then(move |data| {
+ Ok(ZipFile {
+ crypto_reader: None,
+ reader: ZipFileReader::Raw(find_content(data, reader)?),
+ data: Cow::Borrowed(data),
+ })
+ })
+ }
+
+ fn by_index_with_optional_password<'a>(
+ &'a mut self,
+ file_number: usize,
+ mut password: Option<&[u8]>,
+ ) -> ZipResult<Result<ZipFile<'a>, InvalidPassword>> {
+ let data = self
+ .shared
+ .files
+ .get(file_number)
+ .ok_or(ZipError::FileNotFound)?;
+
+ match (password, data.encrypted) {
+ (None, true) => return Err(ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)),
+ (Some(_), false) => password = None, //Password supplied, but none needed! Discard.
+ _ => {}
+ }
+ let limit_reader = find_content(data, &mut self.reader)?;
+
+ match make_crypto_reader(
+ data.compression_method,
+ data.crc32,
+ data.last_modified_time,
+ data.using_data_descriptor,
+ limit_reader,
+ password,
+ data.aes_mode,
+ #[cfg(feature = "aes-crypto")]
+ data.compressed_size,
+ ) {
+ Ok(Ok(crypto_reader)) => Ok(Ok(ZipFile {
+ crypto_reader: Some(crypto_reader),
+ reader: ZipFileReader::NoReader,
+ data: Cow::Borrowed(data),
+ })),
+ Err(e) => Err(e),
+ Ok(Err(e)) => Ok(Err(e)),
+ }
+ }
+
+ /// Unwrap and return the inner reader object
+ ///
+ /// The position of the reader is undefined.
+ pub fn into_inner(self) -> R {
+ self.reader
+ }
+}
+
+fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
+ Err(ZipError::UnsupportedArchive(detail))
+}
+
+/// Parse a central directory entry to collect the information for the file.
+pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
+ reader: &mut R,
+ archive_offset: u64,
+) -> ZipResult<ZipFileData> {
+ let central_header_start = reader.stream_position()?;
+ // Parse central header
+ let signature = reader.read_u32::<LittleEndian>()?;
+ if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
+ return Err(ZipError::InvalidArchive("Invalid Central Directory header"));
+ }
+
+ let version_made_by = reader.read_u16::<LittleEndian>()?;
+ let _version_to_extract = reader.read_u16::<LittleEndian>()?;
+ let flags = reader.read_u16::<LittleEndian>()?;
+ let encrypted = flags & 1 == 1;
+ let is_utf8 = flags & (1 << 11) != 0;
+ let using_data_descriptor = flags & (1 << 3) != 0;
+ let compression_method = reader.read_u16::<LittleEndian>()?;
+ let last_mod_time = reader.read_u16::<LittleEndian>()?;
+ let last_mod_date = reader.read_u16::<LittleEndian>()?;
+ let crc32 = reader.read_u32::<LittleEndian>()?;
+ let compressed_size = reader.read_u32::<LittleEndian>()?;
+ let uncompressed_size = reader.read_u32::<LittleEndian>()?;
+ let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
+ let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
+ let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
+ let _disk_number = reader.read_u16::<LittleEndian>()?;
+ let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
+ let external_file_attributes = reader.read_u32::<LittleEndian>()?;
+ let offset = reader.read_u32::<LittleEndian>()? as u64;
+ let mut file_name_raw = vec![0; file_name_length];
+ reader.read_exact(&mut file_name_raw)?;
+ let mut extra_field = vec![0; extra_field_length];
+ reader.read_exact(&mut extra_field)?;
+ let mut file_comment_raw = vec![0; file_comment_length];
+ reader.read_exact(&mut file_comment_raw)?;
+
+ let file_name = match is_utf8 {
+ true => String::from_utf8_lossy(&file_name_raw).into_owned(),
+ false => file_name_raw.clone().from_cp437(),
+ };
+ let file_comment = match is_utf8 {
+ true => String::from_utf8_lossy(&file_comment_raw).into_owned(),
+ false => file_comment_raw.from_cp437(),
+ };
+
+ // Construct the result
+ let mut result = ZipFileData {
+ system: System::from_u8((version_made_by >> 8) as u8),
+ version_made_by: version_made_by as u8,
+ encrypted,
+ using_data_descriptor,
+ compression_method: {
+ #[allow(deprecated)]
+ CompressionMethod::from_u16(compression_method)
+ },
+ compression_level: None,
+ last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
+ crc32,
+ compressed_size: compressed_size as u64,
+ uncompressed_size: uncompressed_size as u64,
+ file_name,
+ file_name_raw,
+ extra_field,
+ file_comment,
+ header_start: offset,
+ central_header_start,
+ data_start: AtomicU64::new(0),
+ external_attributes: external_file_attributes,
+ large_file: false,
+ aes_mode: None,
+ };
+
+ match parse_extra_field(&mut result) {
+ Ok(..) | Err(ZipError::Io(..)) => {}
+ Err(e) => return Err(e),
+ }
+
+ let aes_enabled = result.compression_method == CompressionMethod::AES;
+ if aes_enabled && result.aes_mode.is_none() {
+ return Err(ZipError::InvalidArchive(
+ "AES encryption without AES extra data field",
+ ));
+ }
+
+ // Account for shifted zip offsets.
+ result.header_start = result
+ .header_start
+ .checked_add(archive_offset)
+ .ok_or(ZipError::InvalidArchive("Archive header is too large"))?;
+
+ Ok(result)
+}
+
+fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
+ let mut reader = io::Cursor::new(&file.extra_field);
+
+ while (reader.position() as usize) < file.extra_field.len() {
+ let kind = reader.read_u16::<LittleEndian>()?;
+ let len = reader.read_u16::<LittleEndian>()?;
+ let mut len_left = len as i64;
+ match kind {
+ // Zip64 extended information extra field
+ 0x0001 => {
+ if file.uncompressed_size == spec::ZIP64_BYTES_THR {
+ file.large_file = true;
+ file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
+ len_left -= 8;
+ }
+ if file.compressed_size == spec::ZIP64_BYTES_THR {
+ file.large_file = true;
+ file.compressed_size = reader.read_u64::<LittleEndian>()?;
+ len_left -= 8;
+ }
+ if file.header_start == spec::ZIP64_BYTES_THR {
+ file.header_start = reader.read_u64::<LittleEndian>()?;
+ len_left -= 8;
+ }
+ }
+ 0x9901 => {
+ // AES
+ if len != 7 {
+ return Err(ZipError::UnsupportedArchive(
+ "AES extra data field has an unsupported length",
+ ));
+ }
+ let vendor_version = reader.read_u16::<LittleEndian>()?;
+ let vendor_id = reader.read_u16::<LittleEndian>()?;
+ let aes_mode = reader.read_u8()?;
+ let compression_method = reader.read_u16::<LittleEndian>()?;
+
+ if vendor_id != 0x4541 {
+ return Err(ZipError::InvalidArchive("Invalid AES vendor"));
+ }
+ let vendor_version = match vendor_version {
+ 0x0001 => AesVendorVersion::Ae1,
+ 0x0002 => AesVendorVersion::Ae2,
+ _ => return Err(ZipError::InvalidArchive("Invalid AES vendor version")),
+ };
+ match aes_mode {
+ 0x01 => file.aes_mode = Some((AesMode::Aes128, vendor_version)),
+ 0x02 => file.aes_mode = Some((AesMode::Aes192, vendor_version)),
+ 0x03 => file.aes_mode = Some((AesMode::Aes256, vendor_version)),
+ _ => return Err(ZipError::InvalidArchive("Invalid AES encryption strength")),
+ };
+ file.compression_method = {
+ #[allow(deprecated)]
+ CompressionMethod::from_u16(compression_method)
+ };
+ }
+ _ => {
+ // Other fields are ignored
+ }
+ }
+
+ // We could also check for < 0 to check for errors
+ if len_left > 0 {
+ reader.seek(io::SeekFrom::Current(len_left))?;
+ }
+ }
+ Ok(())
+}
+
+/// Methods for retrieving information on zip files
+impl<'a> ZipFile<'a> {
+ fn get_reader(&mut self) -> &mut ZipFileReader<'a> {
+ if let ZipFileReader::NoReader = self.reader {
+ let data = &self.data;
+ let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
+ self.reader = make_reader(data.compression_method, data.crc32, crypto_reader)
+ }
+ &mut self.reader
+ }
+
+ pub(crate) fn get_raw_reader(&mut self) -> &mut dyn Read {
+ if let ZipFileReader::NoReader = self.reader {
+ let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
+ self.reader = ZipFileReader::Raw(crypto_reader.into_inner())
+ }
+ &mut self.reader
+ }
+
+ /// Get the version of the file
+ pub fn version_made_by(&self) -> (u8, u8) {
+ (
+ self.data.version_made_by / 10,
+ self.data.version_made_by % 10,
+ )
+ }
+
+ /// Get the name of the file
+ ///
+ /// # Warnings
+ ///
+ /// It is dangerous to use this name directly when extracting an archive.
+ /// It may contain an absolute path (`/etc/shadow`), or break out of the
+ /// current directory (`../runtime`). Carelessly writing to these paths
+ /// allows an attacker to craft a ZIP archive that will overwrite critical
+ /// files.
+ ///
+ /// You can use the [`ZipFile::enclosed_name`] method to validate the name
+ /// as a safe path.
+ pub fn name(&self) -> &str {
+ &self.data.file_name
+ }
+
+ /// Get the name of the file, in the raw (internal) byte representation.
+ ///
+ /// The encoding of this data is currently undefined.
+ pub fn name_raw(&self) -> &[u8] {
+ &self.data.file_name_raw
+ }
+
+ /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte,
+ /// removes a leading '/' and removes '..' parts.
+ #[deprecated(
+ since = "0.5.7",
+ note = "by stripping `..`s from the path, the meaning of paths can change.
+ `mangled_name` can be used if this behaviour is desirable"
+ )]
+ pub fn sanitized_name(&self) -> ::std::path::PathBuf {
+ self.mangled_name()
+ }
+
+ /// Rewrite the path, ignoring any path components with special meaning.
+ ///
+ /// - Absolute paths are made relative
+ /// - [`ParentDir`]s are ignored
+ /// - Truncates the filename at a NULL byte
+ ///
+ /// This is appropriate if you need to be able to extract *something* from
+ /// any archive, but will easily misrepresent trivial paths like
+ /// `foo/../bar` as `foo/bar` (instead of `bar`). Because of this,
+ /// [`ZipFile::enclosed_name`] is the better option in most scenarios.
+ ///
+ /// [`ParentDir`]: `Component::ParentDir`
+ pub fn mangled_name(&self) -> ::std::path::PathBuf {
+ self.data.file_name_sanitized()
+ }
+
+ /// Ensure the file path is safe to use as a [`Path`].
+ ///
+ /// - It can't contain NULL bytes
+ /// - It can't resolve to a path outside the current directory
+ /// > `foo/../bar` is fine, `foo/../../bar` is not.
+ /// - It can't be an absolute path
+ ///
+ /// This will read well-formed ZIP files correctly, and is resistant
+ /// to path-based exploits. It is recommended over
+ /// [`ZipFile::mangled_name`].
+ pub fn enclosed_name(&self) -> Option<&Path> {
+ if self.data.file_name.contains('\0') {
+ return None;
+ }
+ let path = Path::new(&self.data.file_name);
+ let mut depth = 0usize;
+ for component in path.components() {
+ match component {
+ Component::Prefix(_) | Component::RootDir => return None,
+ Component::ParentDir => depth = depth.checked_sub(1)?,
+ Component::Normal(_) => depth += 1,
+ Component::CurDir => (),
+ }
+ }
+ Some(path)
+ }
+
+ /// Get the comment of the file
+ pub fn comment(&self) -> &str {
+ &self.data.file_comment
+ }
+
+ /// Get the compression method used to store the file
+ pub fn compression(&self) -> CompressionMethod {
+ self.data.compression_method
+ }
+
+ /// Get the size of the file, in bytes, in the archive
+ pub fn compressed_size(&self) -> u64 {
+ self.data.compressed_size
+ }
+
+ /// Get the size of the file, in bytes, when uncompressed
+ pub fn size(&self) -> u64 {
+ self.data.uncompressed_size
+ }
+
+ /// Get the time the file was last modified
+ pub fn last_modified(&self) -> DateTime {
+ self.data.last_modified_time
+ }
+ /// Returns whether the file is actually a directory
+ pub fn is_dir(&self) -> bool {
+ self.name()
+ .chars()
+ .rev()
+ .next()
+ .map_or(false, |c| c == '/' || c == '\\')
+ }
+
+ /// Returns whether the file is a regular file
+ pub fn is_file(&self) -> bool {
+ !self.is_dir()
+ }
+
+ /// Get unix mode for the file
+ pub fn unix_mode(&self) -> Option<u32> {
+ if self.data.external_attributes == 0 {
+ return None;
+ }
+
+ match self.data.system {
+ System::Unix => Some(self.data.external_attributes >> 16),
+ System::Dos => {
+ // Interpret MS-DOS directory bit
+ let mut mode = if 0x10 == (self.data.external_attributes & 0x10) {
+ ffi::S_IFDIR | 0o0775
+ } else {
+ ffi::S_IFREG | 0o0664
+ };
+ if 0x01 == (self.data.external_attributes & 0x01) {
+ // Read-only bit; strip write permissions
+ mode &= 0o0555;
+ }
+ Some(mode)
+ }
+ _ => None,
+ }
+ }
+
+ /// Get the CRC32 hash of the original file
+ pub fn crc32(&self) -> u32 {
+ self.data.crc32
+ }
+
+ /// Get the extra data of the zip header for this file
+ pub fn extra_data(&self) -> &[u8] {
+ &self.data.extra_field
+ }
+
+ /// Get the starting offset of the data of the compressed file
+ pub fn data_start(&self) -> u64 {
+ self.data.data_start.load()
+ }
+
+ /// Get the starting offset of the zip header for this file
+ pub fn header_start(&self) -> u64 {
+ self.data.header_start
+ }
+ /// Get the starting offset of the zip header in the central directory for this file
+ pub fn central_header_start(&self) -> u64 {
+ self.data.central_header_start
+ }
+}
+
+impl<'a> Read for ZipFile<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.get_reader().read(buf)
+ }
+}
+
+impl<'a> Drop for ZipFile<'a> {
+ fn drop(&mut self) {
+ // self.data is Owned, this reader is constructed by a streaming reader.
+ // In this case, we want to exhaust the reader so that the next file is accessible.
+ if let Cow::Owned(_) = self.data {
+ let mut buffer = [0; 1 << 16];
+
+ // Get the inner `Take` reader so all decryption, decompression and CRC calculation is skipped.
+ let mut reader: std::io::Take<&mut dyn std::io::Read> = match &mut self.reader {
+ ZipFileReader::NoReader => {
+ let innerreader = ::std::mem::replace(&mut self.crypto_reader, None);
+ innerreader.expect("Invalid reader state").into_inner()
+ }
+ reader => {
+ let innerreader = ::std::mem::replace(reader, ZipFileReader::NoReader);
+ innerreader.into_inner()
+ }
+ };
+
+ loop {
+ match reader.read(&mut buffer) {
+ Ok(0) => break,
+ Ok(_) => (),
+ Err(e) => panic!(
+ "Could not consume all of the output of the current ZipFile: {:?}",
+ e
+ ),
+ }
+ }
+ }
+ }
+}
+
+/// Read ZipFile structures from a non-seekable reader.
+///
+/// This is an alternative method to read a zip file. If possible, use the ZipArchive functions
+/// as some information will be missing when reading this manner.
+///
+/// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is
+/// present at the start of the stream. Returns `Ok(None)` if the start of the central directory
+/// is encountered. No more files should be read after this.
+///
+/// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after
+/// the structure is done.
+///
+/// Missing fields are:
+/// * `comment`: set to an empty string
+/// * `data_start`: set to 0
+/// * `external_attributes`: `unix_mode()`: will return None
+pub fn read_zipfile_from_stream<'a, R: io::Read>(
+ reader: &'a mut R,
+) -> ZipResult<Option<ZipFile<'_>>> {
+ let signature = reader.read_u32::<LittleEndian>()?;
+
+ match signature {
+ spec::LOCAL_FILE_HEADER_SIGNATURE => (),
+ spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None),
+ _ => return Err(ZipError::InvalidArchive("Invalid local file header")),
+ }
+
+ let version_made_by = reader.read_u16::<LittleEndian>()?;
+ let flags = reader.read_u16::<LittleEndian>()?;
+ let encrypted = flags & 1 == 1;
+ let is_utf8 = flags & (1 << 11) != 0;
+ let using_data_descriptor = flags & (1 << 3) != 0;
+ #[allow(deprecated)]
+ let compression_method = CompressionMethod::from_u16(reader.read_u16::<LittleEndian>()?);
+ let last_mod_time = reader.read_u16::<LittleEndian>()?;
+ let last_mod_date = reader.read_u16::<LittleEndian>()?;
+ let crc32 = reader.read_u32::<LittleEndian>()?;
+ let compressed_size = reader.read_u32::<LittleEndian>()?;
+ let uncompressed_size = reader.read_u32::<LittleEndian>()?;
+ let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
+ let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
+
+ let mut file_name_raw = vec![0; file_name_length];
+ reader.read_exact(&mut file_name_raw)?;
+ let mut extra_field = vec![0; extra_field_length];
+ reader.read_exact(&mut extra_field)?;
+
+ let file_name = match is_utf8 {
+ true => String::from_utf8_lossy(&file_name_raw).into_owned(),
+ false => file_name_raw.clone().from_cp437(),
+ };
+
+ let mut result = ZipFileData {
+ system: System::from_u8((version_made_by >> 8) as u8),
+ version_made_by: version_made_by as u8,
+ encrypted,
+ using_data_descriptor,
+ compression_method,
+ compression_level: None,
+ last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
+ crc32,
+ compressed_size: compressed_size as u64,
+ uncompressed_size: uncompressed_size as u64,
+ file_name,
+ file_name_raw,
+ extra_field,
+ file_comment: String::new(), // file comment is only available in the central directory
+ // header_start and data start are not available, but also don't matter, since seeking is
+ // not available.
+ header_start: 0,
+ data_start: AtomicU64::new(0),
+ central_header_start: 0,
+ // The external_attributes field is only available in the central directory.
+ // We set this to zero, which should be valid as the docs state 'If input came
+ // from standard input, this field is set to zero.'
+ external_attributes: 0,
+ large_file: false,
+ aes_mode: None,
+ };
+
+ match parse_extra_field(&mut result) {
+ Ok(..) | Err(ZipError::Io(..)) => {}
+ Err(e) => return Err(e),
+ }
+
+ if encrypted {
+ return unsupported_zip_error("Encrypted files are not supported");
+ }
+ if using_data_descriptor {
+ return unsupported_zip_error("The file length is not available in the local header");
+ }
+
+ let limit_reader = (reader as &'a mut dyn io::Read).take(result.compressed_size);
+
+ let result_crc32 = result.crc32;
+ let result_compression_method = result.compression_method;
+ let crypto_reader = make_crypto_reader(
+ result_compression_method,
+ result_crc32,
+ result.last_modified_time,
+ result.using_data_descriptor,
+ limit_reader,
+ None,
+ None,
+ #[cfg(feature = "aes-crypto")]
+ result.compressed_size,
+ )?
+ .unwrap();
+
+ Ok(Some(ZipFile {
+ data: Cow::Owned(result),
+ crypto_reader: None,
+ reader: make_reader(result_compression_method, result_crc32, crypto_reader),
+ }))
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn invalid_offset() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip"));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
+
+ #[test]
+ fn invalid_offset2() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/invalid_offset2.zip"));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
+
+ #[test]
+ fn zip64_with_leading_junk() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip"));
+ let reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
+ assert_eq!(reader.len(), 1);
+ }
+
+ #[test]
+ fn zip_contents() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
+ let mut reader = ZipArchive::new(io::Cursor::new(v)).unwrap();
+ assert_eq!(reader.comment(), b"");
+ assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77);
+ }
+
+ #[test]
+ fn zip_read_streaming() {
+ use super::read_zipfile_from_stream;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
+ let mut reader = io::Cursor::new(v);
+ loop {
+ if read_zipfile_from_stream(&mut reader).unwrap().is_none() {
+ break;
+ }
+ }
+ }
+
+ #[test]
+ fn zip_clone() {
+ use super::ZipArchive;
+ use std::io::{self, Read};
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
+ let mut reader1 = ZipArchive::new(io::Cursor::new(v)).unwrap();
+ let mut reader2 = reader1.clone();
+
+ let mut file1 = reader1.by_index(0).unwrap();
+ let mut file2 = reader2.by_index(0).unwrap();
+
+ let t = file1.last_modified();
+ assert_eq!(
+ (
+ t.year(),
+ t.month(),
+ t.day(),
+ t.hour(),
+ t.minute(),
+ t.second()
+ ),
+ (1980, 1, 1, 0, 0, 0)
+ );
+
+ let mut buf1 = [0; 5];
+ let mut buf2 = [0; 5];
+ let mut buf3 = [0; 5];
+ let mut buf4 = [0; 5];
+
+ file1.read_exact(&mut buf1).unwrap();
+ file2.read_exact(&mut buf2).unwrap();
+ file1.read_exact(&mut buf3).unwrap();
+ file2.read_exact(&mut buf4).unwrap();
+
+ assert_eq!(buf1, buf2);
+ assert_eq!(buf3, buf4);
+ assert_ne!(buf1, buf3);
+ }
+
+ #[test]
+ fn file_and_dir_predicates() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/files_and_dirs.zip"));
+ let mut zip = ZipArchive::new(io::Cursor::new(v)).unwrap();
+
+ for i in 0..zip.len() {
+ let zip_file = zip.by_index(i).unwrap();
+ let full_name = zip_file.enclosed_name().unwrap();
+ let file_name = full_name.file_name().unwrap().to_str().unwrap();
+ assert!(
+ (file_name.starts_with("dir") && zip_file.is_dir())
+ || (file_name.starts_with("file") && zip_file.is_file())
+ );
+ }
+ }
+
+ /// test case to ensure we don't preemptively over allocate based on the
+ /// declared number of files in the CDE of an invalid zip when the number of
+ /// files declared is more than the alleged offset in the CDE
+ #[test]
+ fn invalid_cde_number_of_files_allocation_smaller_offset() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!(
+ "../tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip"
+ ));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
+
+ /// test case to ensure we don't preemptively over allocate based on the
+ /// declared number of files in the CDE of an invalid zip when the number of
+ /// files declared is less than the alleged offset in the CDE
+ #[test]
+ fn invalid_cde_number_of_files_allocation_greater_offset() {
+ use super::ZipArchive;
+ use std::io;
+
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!(
+ "../tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip"
+ ));
+ let reader = ZipArchive::new(io::Cursor::new(v));
+ assert!(reader.is_err());
+ }
+}
diff --git a/third_party/rust/zip/src/result.rs b/third_party/rust/zip/src/result.rs
new file mode 100644
index 0000000000..00d558cb42
--- /dev/null
+++ b/third_party/rust/zip/src/result.rs
@@ -0,0 +1,98 @@
+//! Error types that can be emitted from this library
+
+use std::error::Error;
+use std::fmt;
+use std::io;
+
+/// Generic result type with ZipError as its error variant
+pub type ZipResult<T> = Result<T, ZipError>;
+
+/// The given password is wrong
+#[derive(Debug)]
+pub struct InvalidPassword;
+
+impl fmt::Display for InvalidPassword {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(fmt, "invalid password for file in archive")
+ }
+}
+
+impl Error for InvalidPassword {}
+
+/// Error type for Zip
+#[derive(Debug)]
+pub enum ZipError {
+ /// An Error caused by I/O
+ Io(io::Error),
+
+ /// This file is probably not a zip archive
+ InvalidArchive(&'static str),
+
+ /// This archive is not supported
+ UnsupportedArchive(&'static str),
+
+ /// The requested file could not be found in the archive
+ FileNotFound,
+}
+
+impl From<io::Error> for ZipError {
+ fn from(err: io::Error) -> ZipError {
+ ZipError::Io(err)
+ }
+}
+
+impl fmt::Display for ZipError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ZipError::Io(err) => write!(fmt, "{err}"),
+ ZipError::InvalidArchive(err) => write!(fmt, "invalid Zip archive: {err}"),
+ ZipError::UnsupportedArchive(err) => write!(fmt, "unsupported Zip archive: {err}"),
+ ZipError::FileNotFound => write!(fmt, "specified file not found in archive"),
+ }
+ }
+}
+
+impl Error for ZipError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ ZipError::Io(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl ZipError {
+ /// The text used as an error when a password is required and not supplied
+ ///
+ /// ```rust,no_run
+ /// # use zip::result::ZipError;
+ /// # let mut archive = zip::ZipArchive::new(std::io::Cursor::new(&[])).unwrap();
+ /// match archive.by_index(1) {
+ /// Err(ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)) => eprintln!("a password is needed to unzip this file"),
+ /// _ => (),
+ /// }
+ /// # ()
+ /// ```
+ pub const PASSWORD_REQUIRED: &'static str = "Password required to decrypt file";
+}
+
+impl From<ZipError> for io::Error {
+ fn from(err: ZipError) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, err)
+ }
+}
+
+/// Error type for time parsing
+#[derive(Debug)]
+pub struct DateTimeRangeError;
+
+impl fmt::Display for DateTimeRangeError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ fmt,
+ "a date could not be represented within the bounds the MS-DOS date range (1980-2107)"
+ )
+ }
+}
+
+impl Error for DateTimeRangeError {}
diff --git a/third_party/rust/zip/src/spec.rs b/third_party/rust/zip/src/spec.rs
new file mode 100644
index 0000000000..1d8cb0a643
--- /dev/null
+++ b/third_party/rust/zip/src/spec.rs
@@ -0,0 +1,219 @@
+use crate::result::{ZipError, ZipResult};
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use std::io;
+use std::io::prelude::*;
+
+pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
+pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
+const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50;
+pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
+const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
+
+pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
+pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize;
+
+pub struct CentralDirectoryEnd {
+ pub disk_number: u16,
+ pub disk_with_central_directory: u16,
+ pub number_of_files_on_this_disk: u16,
+ pub number_of_files: u16,
+ pub central_directory_size: u32,
+ pub central_directory_offset: u32,
+ pub zip_file_comment: Vec<u8>,
+}
+
+impl CentralDirectoryEnd {
+ // Per spec 4.4.1.4 - a CentralDirectoryEnd field might be insufficient to hold the
+ // required data. In this case the file SHOULD contain a ZIP64 format record
+ // and the field of this record will be set to -1
+ pub(crate) fn record_too_small(&self) -> bool {
+ self.disk_number == 0xFFFF
+ || self.disk_with_central_directory == 0xFFFF
+ || self.number_of_files_on_this_disk == 0xFFFF
+ || self.number_of_files == 0xFFFF
+ || self.central_directory_size == 0xFFFFFFFF
+ || self.central_directory_offset == 0xFFFFFFFF
+ }
+
+ pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
+ let magic = reader.read_u32::<LittleEndian>()?;
+ if magic != CENTRAL_DIRECTORY_END_SIGNATURE {
+ return Err(ZipError::InvalidArchive("Invalid digital signature header"));
+ }
+ let disk_number = reader.read_u16::<LittleEndian>()?;
+ let disk_with_central_directory = reader.read_u16::<LittleEndian>()?;
+ let number_of_files_on_this_disk = reader.read_u16::<LittleEndian>()?;
+ let number_of_files = reader.read_u16::<LittleEndian>()?;
+ let central_directory_size = reader.read_u32::<LittleEndian>()?;
+ let central_directory_offset = reader.read_u32::<LittleEndian>()?;
+ let zip_file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
+ let mut zip_file_comment = vec![0; zip_file_comment_length];
+ reader.read_exact(&mut zip_file_comment)?;
+
+ Ok(CentralDirectoryEnd {
+ disk_number,
+ disk_with_central_directory,
+ number_of_files_on_this_disk,
+ number_of_files,
+ central_directory_size,
+ central_directory_offset,
+ zip_file_comment,
+ })
+ }
+
+ pub fn find_and_parse<T: Read + io::Seek>(
+ reader: &mut T,
+ ) -> ZipResult<(CentralDirectoryEnd, u64)> {
+ const HEADER_SIZE: u64 = 22;
+ const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6;
+ let file_length = reader.seek(io::SeekFrom::End(0))?;
+
+ let search_upper_bound = file_length.saturating_sub(HEADER_SIZE + ::std::u16::MAX as u64);
+
+ if file_length < HEADER_SIZE {
+ return Err(ZipError::InvalidArchive("Invalid zip header"));
+ }
+
+ let mut pos = file_length - HEADER_SIZE;
+ while pos >= search_upper_bound {
+ reader.seek(io::SeekFrom::Start(pos))?;
+ if reader.read_u32::<LittleEndian>()? == CENTRAL_DIRECTORY_END_SIGNATURE {
+ reader.seek(io::SeekFrom::Current(
+ BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64,
+ ))?;
+ let cde_start_pos = reader.seek(io::SeekFrom::Start(pos))?;
+ return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos));
+ }
+ pos = match pos.checked_sub(1) {
+ Some(p) => p,
+ None => break,
+ };
+ }
+ Err(ZipError::InvalidArchive(
+ "Could not find central directory end",
+ ))
+ }
+
+ pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
+ writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)?;
+ writer.write_u16::<LittleEndian>(self.disk_number)?;
+ writer.write_u16::<LittleEndian>(self.disk_with_central_directory)?;
+ writer.write_u16::<LittleEndian>(self.number_of_files_on_this_disk)?;
+ writer.write_u16::<LittleEndian>(self.number_of_files)?;
+ writer.write_u32::<LittleEndian>(self.central_directory_size)?;
+ writer.write_u32::<LittleEndian>(self.central_directory_offset)?;
+ writer.write_u16::<LittleEndian>(self.zip_file_comment.len() as u16)?;
+ writer.write_all(&self.zip_file_comment)?;
+ Ok(())
+ }
+}
+
+pub struct Zip64CentralDirectoryEndLocator {
+ pub disk_with_central_directory: u32,
+ pub end_of_central_directory_offset: u64,
+ pub number_of_disks: u32,
+}
+
+impl Zip64CentralDirectoryEndLocator {
+ pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> {
+ let magic = reader.read_u32::<LittleEndian>()?;
+ if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE {
+ return Err(ZipError::InvalidArchive(
+ "Invalid zip64 locator digital signature header",
+ ));
+ }
+ let disk_with_central_directory = reader.read_u32::<LittleEndian>()?;
+ let end_of_central_directory_offset = reader.read_u64::<LittleEndian>()?;
+ let number_of_disks = reader.read_u32::<LittleEndian>()?;
+
+ Ok(Zip64CentralDirectoryEndLocator {
+ disk_with_central_directory,
+ end_of_central_directory_offset,
+ number_of_disks,
+ })
+ }
+
+ pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
+ writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE)?;
+ writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?;
+ writer.write_u64::<LittleEndian>(self.end_of_central_directory_offset)?;
+ writer.write_u32::<LittleEndian>(self.number_of_disks)?;
+ Ok(())
+ }
+}
+
+pub struct Zip64CentralDirectoryEnd {
+ pub version_made_by: u16,
+ pub version_needed_to_extract: u16,
+ pub disk_number: u32,
+ pub disk_with_central_directory: u32,
+ pub number_of_files_on_this_disk: u64,
+ pub number_of_files: u64,
+ pub central_directory_size: u64,
+ pub central_directory_offset: u64,
+ //pub extensible_data_sector: Vec<u8>, <-- We don't do anything with this at the moment.
+}
+
+impl Zip64CentralDirectoryEnd {
+ pub fn find_and_parse<T: Read + io::Seek>(
+ reader: &mut T,
+ nominal_offset: u64,
+ search_upper_bound: u64,
+ ) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> {
+ let mut pos = nominal_offset;
+
+ while pos <= search_upper_bound {
+ reader.seek(io::SeekFrom::Start(pos))?;
+
+ if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE {
+ let archive_offset = pos - nominal_offset;
+
+ let _record_size = reader.read_u64::<LittleEndian>()?;
+ // We would use this value if we did anything with the "zip64 extensible data sector".
+
+ let version_made_by = reader.read_u16::<LittleEndian>()?;
+ let version_needed_to_extract = reader.read_u16::<LittleEndian>()?;
+ let disk_number = reader.read_u32::<LittleEndian>()?;
+ let disk_with_central_directory = reader.read_u32::<LittleEndian>()?;
+ let number_of_files_on_this_disk = reader.read_u64::<LittleEndian>()?;
+ let number_of_files = reader.read_u64::<LittleEndian>()?;
+ let central_directory_size = reader.read_u64::<LittleEndian>()?;
+ let central_directory_offset = reader.read_u64::<LittleEndian>()?;
+
+ return Ok((
+ Zip64CentralDirectoryEnd {
+ version_made_by,
+ version_needed_to_extract,
+ disk_number,
+ disk_with_central_directory,
+ number_of_files_on_this_disk,
+ number_of_files,
+ central_directory_size,
+ central_directory_offset,
+ },
+ archive_offset,
+ ));
+ }
+
+ pos += 1;
+ }
+
+ Err(ZipError::InvalidArchive(
+ "Could not find ZIP64 central directory end",
+ ))
+ }
+
+ pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
+ writer.write_u32::<LittleEndian>(ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE)?;
+ writer.write_u64::<LittleEndian>(44)?; // record size
+ writer.write_u16::<LittleEndian>(self.version_made_by)?;
+ writer.write_u16::<LittleEndian>(self.version_needed_to_extract)?;
+ writer.write_u32::<LittleEndian>(self.disk_number)?;
+ writer.write_u32::<LittleEndian>(self.disk_with_central_directory)?;
+ writer.write_u64::<LittleEndian>(self.number_of_files_on_this_disk)?;
+ writer.write_u64::<LittleEndian>(self.number_of_files)?;
+ writer.write_u64::<LittleEndian>(self.central_directory_size)?;
+ writer.write_u64::<LittleEndian>(self.central_directory_offset)?;
+ Ok(())
+ }
+}
diff --git a/third_party/rust/zip/src/types.rs b/third_party/rust/zip/src/types.rs
new file mode 100644
index 0000000000..ad3a5700b2
--- /dev/null
+++ b/third_party/rust/zip/src/types.rs
@@ -0,0 +1,608 @@
+//! Types that specify what is contained in a ZIP.
+#[cfg(feature = "time")]
+use std::convert::{TryFrom, TryInto};
+#[cfg(not(any(
+ all(target_arch = "arm", target_pointer_width = "32"),
+ target_arch = "mips",
+ target_arch = "powerpc"
+)))]
+use std::sync::atomic;
+#[cfg(not(feature = "time"))]
+use std::time::SystemTime;
+#[cfg(doc)]
+use {crate::read::ZipFile, crate::write::FileOptions};
+
+#[cfg(any(
+ all(target_arch = "arm", target_pointer_width = "32"),
+ target_arch = "mips",
+ target_arch = "powerpc"
+))]
+mod atomic {
+ use crossbeam_utils::sync::ShardedLock;
+ pub use std::sync::atomic::Ordering;
+
+ #[derive(Debug, Default)]
+ pub struct AtomicU64 {
+ value: ShardedLock<u64>,
+ }
+
+ impl AtomicU64 {
+ pub fn new(v: u64) -> Self {
+ Self {
+ value: ShardedLock::new(v),
+ }
+ }
+ pub fn get_mut(&mut self) -> &mut u64 {
+ self.value.get_mut().unwrap()
+ }
+ pub fn load(&self, _: Ordering) -> u64 {
+ *self.value.read().unwrap()
+ }
+ pub fn store(&self, value: u64, _: Ordering) {
+ *self.value.write().unwrap() = value;
+ }
+ }
+}
+
+#[cfg(feature = "time")]
+use crate::result::DateTimeRangeError;
+#[cfg(feature = "time")]
+use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum System {
+ Dos = 0,
+ Unix = 3,
+ Unknown,
+}
+
+impl System {
+ pub fn from_u8(system: u8) -> System {
+ use self::System::*;
+
+ match system {
+ 0 => Dos,
+ 3 => Unix,
+ _ => Unknown,
+ }
+ }
+}
+
+/// Representation of a moment in time.
+///
+/// Zip files use an old format from DOS to store timestamps,
+/// with its own set of peculiarities.
+/// For example, it has a resolution of 2 seconds!
+///
+/// A [`DateTime`] can be stored directly in a zipfile with [`FileOptions::last_modified_time`],
+/// or read from one with [`ZipFile::last_modified`]
+///
+/// # Warning
+///
+/// Because there is no timezone associated with the [`DateTime`], they should ideally only
+/// be used for user-facing descriptions. This also means [`DateTime::to_time`] returns an
+/// [`OffsetDateTime`] (which is the equivalent of chrono's `NaiveDateTime`).
+///
+/// Modern zip files store more precise timestamps, which are ignored by [`crate::read::ZipArchive`],
+/// so keep in mind that these timestamps are unreliable. [We're working on this](https://github.com/zip-rs/zip/issues/156#issuecomment-652981904).
+#[derive(Debug, Clone, Copy)]
+pub struct DateTime {
+ year: u16,
+ month: u8,
+ day: u8,
+ hour: u8,
+ minute: u8,
+ second: u8,
+}
+
+impl ::std::default::Default for DateTime {
+ /// Constructs an 'default' datetime of 1980-01-01 00:00:00
+ fn default() -> DateTime {
+ DateTime {
+ year: 1980,
+ month: 1,
+ day: 1,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ }
+ }
+}
+
+impl DateTime {
+ /// Converts an msdos (u16, u16) pair to a DateTime object
+ pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime {
+ let seconds = (timepart & 0b0000000000011111) << 1;
+ let minutes = (timepart & 0b0000011111100000) >> 5;
+ let hours = (timepart & 0b1111100000000000) >> 11;
+ let days = datepart & 0b0000000000011111;
+ let months = (datepart & 0b0000000111100000) >> 5;
+ let years = (datepart & 0b1111111000000000) >> 9;
+
+ DateTime {
+ year: years + 1980,
+ month: months as u8,
+ day: days as u8,
+ hour: hours as u8,
+ minute: minutes as u8,
+ second: seconds as u8,
+ }
+ }
+
+ /// Constructs a DateTime from a specific date and time
+ ///
+ /// The bounds are:
+ /// * year: [1980, 2107]
+ /// * month: [1, 12]
+ /// * day: [1, 31]
+ /// * hour: [0, 23]
+ /// * minute: [0, 59]
+ /// * second: [0, 60]
+ #[allow(clippy::result_unit_err)]
+ pub fn from_date_and_time(
+ year: u16,
+ month: u8,
+ day: u8,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ ) -> Result<DateTime, ()> {
+ if (1980..=2107).contains(&year)
+ && (1..=12).contains(&month)
+ && (1..=31).contains(&day)
+ && hour <= 23
+ && minute <= 59
+ && second <= 60
+ {
+ Ok(DateTime {
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ })
+ } else {
+ Err(())
+ }
+ }
+
+ #[cfg(feature = "time")]
+ /// Converts a OffsetDateTime object to a DateTime
+ ///
+ /// Returns `Err` when this object is out of bounds
+ #[allow(clippy::result_unit_err)]
+ #[deprecated(note = "use `DateTime::try_from()`")]
+ pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
+ dt.try_into().map_err(|_err| ())
+ }
+
+ /// Gets the time portion of this datetime in the msdos representation
+ pub fn timepart(&self) -> u16 {
+ ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
+ }
+
+ /// Gets the date portion of this datetime in the msdos representation
+ pub fn datepart(&self) -> u16 {
+ (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
+ }
+
+ #[cfg(feature = "time")]
+ /// Converts the DateTime to a OffsetDateTime structure
+ pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
+ let date =
+ Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
+ let time = Time::from_hms(self.hour, self.minute, self.second)?;
+ Ok(PrimitiveDateTime::new(date, time).assume_utc())
+ }
+
+ /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018.
+ pub fn year(&self) -> u16 {
+ self.year
+ }
+
+ /// Get the month, where 1 = january and 12 = december
+ ///
+ /// # Warning
+ ///
+ /// When read from a zip file, this may not be a reasonable value
+ pub fn month(&self) -> u8 {
+ self.month
+ }
+
+ /// Get the day
+ ///
+ /// # Warning
+ ///
+ /// When read from a zip file, this may not be a reasonable value
+ pub fn day(&self) -> u8 {
+ self.day
+ }
+
+ /// Get the hour
+ ///
+ /// # Warning
+ ///
+ /// When read from a zip file, this may not be a reasonable value
+ pub fn hour(&self) -> u8 {
+ self.hour
+ }
+
+ /// Get the minute
+ ///
+ /// # Warning
+ ///
+ /// When read from a zip file, this may not be a reasonable value
+ pub fn minute(&self) -> u8 {
+ self.minute
+ }
+
+ /// Get the second
+ ///
+ /// # Warning
+ ///
+ /// When read from a zip file, this may not be a reasonable value
+ pub fn second(&self) -> u8 {
+ self.second
+ }
+}
+
+#[cfg(feature = "time")]
+impl TryFrom<OffsetDateTime> for DateTime {
+ type Error = DateTimeRangeError;
+
+ fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
+ if dt.year() >= 1980 && dt.year() <= 2107 {
+ Ok(DateTime {
+ year: (dt.year()) as u16,
+ month: (dt.month()) as u8,
+ day: dt.day(),
+ hour: dt.hour(),
+ minute: dt.minute(),
+ second: dt.second(),
+ })
+ } else {
+ Err(DateTimeRangeError)
+ }
+ }
+}
+
+pub const DEFAULT_VERSION: u8 = 46;
+
+/// A type like `AtomicU64` except it implements `Clone` and has predefined
+/// ordering.
+///
+/// It uses `Relaxed` ordering because it is not used for synchronisation.
+#[derive(Debug)]
+pub struct AtomicU64(atomic::AtomicU64);
+
+impl AtomicU64 {
+ pub fn new(v: u64) -> Self {
+ Self(atomic::AtomicU64::new(v))
+ }
+
+ pub fn load(&self) -> u64 {
+ self.0.load(atomic::Ordering::Relaxed)
+ }
+
+ pub fn store(&self, val: u64) {
+ self.0.store(val, atomic::Ordering::Relaxed)
+ }
+
+ pub fn get_mut(&mut self) -> &mut u64 {
+ self.0.get_mut()
+ }
+}
+
+impl Clone for AtomicU64 {
+ fn clone(&self) -> Self {
+ Self(atomic::AtomicU64::new(self.load()))
+ }
+}
+
+/// Structure representing a ZIP file.
+#[derive(Debug, Clone)]
+pub struct ZipFileData {
+ /// Compatibility of the file attribute information
+ pub system: System,
+ /// Specification version
+ pub version_made_by: u8,
+ /// True if the file is encrypted.
+ pub encrypted: bool,
+ /// True if the file uses a data-descriptor section
+ pub using_data_descriptor: bool,
+ /// Compression method used to store the file
+ pub compression_method: crate::compression::CompressionMethod,
+ /// Compression level to store the file
+ pub compression_level: Option<i32>,
+ /// Last modified time. This will only have a 2 second precision.
+ pub last_modified_time: DateTime,
+ /// CRC32 checksum
+ pub crc32: u32,
+ /// Size of the file in the ZIP
+ pub compressed_size: u64,
+ /// Size of the file when extracted
+ pub uncompressed_size: u64,
+ /// Name of the file
+ pub file_name: String,
+ /// Raw file name. To be used when file_name was incorrectly decoded.
+ pub file_name_raw: Vec<u8>,
+ /// Extra field usually used for storage expansion
+ pub extra_field: Vec<u8>,
+ /// File comment
+ pub file_comment: String,
+ /// Specifies where the local header of the file starts
+ pub header_start: u64,
+ /// Specifies where the central header of the file starts
+ ///
+ /// Note that when this is not known, it is set to 0
+ pub central_header_start: u64,
+ /// Specifies where the compressed data of the file starts
+ pub data_start: AtomicU64,
+ /// External file attributes
+ pub external_attributes: u32,
+ /// Reserve local ZIP64 extra field
+ pub large_file: bool,
+ /// AES mode if applicable
+ pub aes_mode: Option<(AesMode, AesVendorVersion)>,
+}
+
+impl ZipFileData {
+ pub fn file_name_sanitized(&self) -> ::std::path::PathBuf {
+ let no_null_filename = match self.file_name.find('\0') {
+ Some(index) => &self.file_name[0..index],
+ None => &self.file_name,
+ }
+ .to_string();
+
+ // zip files can contain both / and \ as separators regardless of the OS
+ // and as we want to return a sanitized PathBuf that only supports the
+ // OS separator let's convert incompatible separators to compatible ones
+ let separator = ::std::path::MAIN_SEPARATOR;
+ let opposite_separator = match separator {
+ '/' => '\\',
+ _ => '/',
+ };
+ let filename =
+ no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
+
+ ::std::path::Path::new(&filename)
+ .components()
+ .filter(|component| matches!(*component, ::std::path::Component::Normal(..)))
+ .fold(::std::path::PathBuf::new(), |mut path, ref cur| {
+ path.push(cur.as_os_str());
+ path
+ })
+ }
+
+ pub fn zip64_extension(&self) -> bool {
+ self.uncompressed_size > 0xFFFFFFFF
+ || self.compressed_size > 0xFFFFFFFF
+ || self.header_start > 0xFFFFFFFF
+ }
+
+ pub fn version_needed(&self) -> u16 {
+ // higher versions matched first
+ match (self.zip64_extension(), self.compression_method) {
+ #[cfg(feature = "bzip2")]
+ (_, crate::compression::CompressionMethod::Bzip2) => 46,
+ (true, _) => 45,
+ _ => 20,
+ }
+ }
+}
+
+/// The encryption specification used to encrypt a file with AES.
+///
+/// According to the [specification](https://www.winzip.com/win/en/aes_info.html#winzip11) AE-2
+/// does not make use of the CRC check.
+#[derive(Copy, Clone, Debug)]
+pub enum AesVendorVersion {
+ Ae1,
+ Ae2,
+}
+
+/// AES variant used.
+#[derive(Copy, Clone, Debug)]
+pub enum AesMode {
+ Aes128,
+ Aes192,
+ Aes256,
+}
+
+#[cfg(feature = "aes-crypto")]
+impl AesMode {
+ pub fn salt_length(&self) -> usize {
+ self.key_length() / 2
+ }
+
+ pub fn key_length(&self) -> usize {
+ match self {
+ Self::Aes128 => 16,
+ Self::Aes192 => 24,
+ Self::Aes256 => 32,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn system() {
+ use super::System;
+ assert_eq!(System::Dos as u16, 0u16);
+ assert_eq!(System::Unix as u16, 3u16);
+ assert_eq!(System::from_u8(0), System::Dos);
+ assert_eq!(System::from_u8(3), System::Unix);
+ }
+
+ #[test]
+ fn sanitize() {
+ use super::*;
+ let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
+ let data = ZipFileData {
+ system: System::Dos,
+ version_made_by: 0,
+ encrypted: false,
+ using_data_descriptor: false,
+ compression_method: crate::compression::CompressionMethod::Stored,
+ compression_level: None,
+ last_modified_time: DateTime::default(),
+ crc32: 0,
+ compressed_size: 0,
+ uncompressed_size: 0,
+ file_name: file_name.clone(),
+ file_name_raw: file_name.into_bytes(),
+ extra_field: Vec::new(),
+ file_comment: String::new(),
+ header_start: 0,
+ data_start: AtomicU64::new(0),
+ central_header_start: 0,
+ external_attributes: 0,
+ large_file: false,
+ aes_mode: None,
+ };
+ assert_eq!(
+ data.file_name_sanitized(),
+ ::std::path::PathBuf::from("path/etc/passwd")
+ );
+ }
+
+ #[test]
+ #[allow(clippy::unusual_byte_groupings)]
+ fn datetime_default() {
+ use super::DateTime;
+ let dt = DateTime::default();
+ assert_eq!(dt.timepart(), 0);
+ assert_eq!(dt.datepart(), 0b0000000_0001_00001);
+ }
+
+ #[test]
+ #[allow(clippy::unusual_byte_groupings)]
+ fn datetime_max() {
+ use super::DateTime;
+ let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
+ assert_eq!(dt.timepart(), 0b10111_111011_11110);
+ assert_eq!(dt.datepart(), 0b1111111_1100_11111);
+ }
+
+ #[test]
+ fn datetime_bounds() {
+ use super::DateTime;
+
+ assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
+ assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
+ assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
+
+ assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
+ assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
+ assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
+ assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
+ }
+
+ #[cfg(feature = "time")]
+ use time::{format_description::well_known::Rfc3339, OffsetDateTime};
+
+ #[cfg(feature = "time")]
+ #[test]
+ fn datetime_from_time_bounds() {
+ use std::convert::TryFrom;
+
+ use super::DateTime;
+ use time::macros::datetime;
+
+ // 1979-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
+
+ // 1980-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
+
+ // 2107-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
+
+ // 2108-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
+ }
+
+ #[cfg(feature = "time")]
+ #[test]
+ fn datetime_try_from_bounds() {
+ use std::convert::TryFrom;
+
+ use super::DateTime;
+ use time::macros::datetime;
+
+ // 1979-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
+
+ // 1980-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
+
+ // 2107-12-31 23:59:59
+ assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
+
+ // 2108-01-01 00:00:00
+ assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
+ }
+
+ #[test]
+ fn time_conversion() {
+ use super::DateTime;
+ let dt = DateTime::from_msdos(0x4D71, 0x54CF);
+ assert_eq!(dt.year(), 2018);
+ assert_eq!(dt.month(), 11);
+ assert_eq!(dt.day(), 17);
+ assert_eq!(dt.hour(), 10);
+ assert_eq!(dt.minute(), 38);
+ assert_eq!(dt.second(), 30);
+
+ #[cfg(feature = "time")]
+ assert_eq!(
+ dt.to_time().unwrap().format(&Rfc3339).unwrap(),
+ "2018-11-17T10:38:30Z"
+ );
+ }
+
+ #[test]
+ fn time_out_of_bounds() {
+ use super::DateTime;
+ let dt = DateTime::from_msdos(0xFFFF, 0xFFFF);
+ assert_eq!(dt.year(), 2107);
+ assert_eq!(dt.month(), 15);
+ assert_eq!(dt.day(), 31);
+ assert_eq!(dt.hour(), 31);
+ assert_eq!(dt.minute(), 63);
+ assert_eq!(dt.second(), 62);
+
+ #[cfg(feature = "time")]
+ assert!(dt.to_time().is_err());
+
+ let dt = DateTime::from_msdos(0x0000, 0x0000);
+ assert_eq!(dt.year(), 1980);
+ assert_eq!(dt.month(), 0);
+ assert_eq!(dt.day(), 0);
+ assert_eq!(dt.hour(), 0);
+ assert_eq!(dt.minute(), 0);
+ assert_eq!(dt.second(), 0);
+
+ #[cfg(feature = "time")]
+ assert!(dt.to_time().is_err());
+ }
+
+ #[cfg(feature = "time")]
+ #[test]
+ fn time_at_january() {
+ use super::DateTime;
+ use std::convert::TryFrom;
+
+ // 2020-01-01 00:00:00
+ let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
+
+ assert!(DateTime::try_from(clock).is_ok());
+ }
+}
diff --git a/third_party/rust/zip/src/write.rs b/third_party/rust/zip/src/write.rs
new file mode 100644
index 0000000000..14252b4d59
--- /dev/null
+++ b/third_party/rust/zip/src/write.rs
@@ -0,0 +1,1467 @@
+//! Types for creating ZIP archives
+
+use crate::compression::CompressionMethod;
+use crate::read::{central_header_to_zip_file, ZipArchive, ZipFile};
+use crate::result::{ZipError, ZipResult};
+use crate::spec;
+use crate::types::{AtomicU64, DateTime, System, ZipFileData, DEFAULT_VERSION};
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use crc32fast::Hasher;
+use std::convert::TryInto;
+use std::default::Default;
+use std::io;
+use std::io::prelude::*;
+use std::mem;
+
+#[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+))]
+use flate2::write::DeflateEncoder;
+
+#[cfg(feature = "bzip2")]
+use bzip2::write::BzEncoder;
+
+#[cfg(feature = "time")]
+use time::OffsetDateTime;
+
+#[cfg(feature = "zstd")]
+use zstd::stream::write::Encoder as ZstdEncoder;
+
+enum GenericZipWriter<W: Write + io::Seek> {
+ Closed,
+ Storer(W),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ Deflater(DeflateEncoder<W>),
+ #[cfg(feature = "bzip2")]
+ Bzip2(BzEncoder<W>),
+ #[cfg(feature = "zstd")]
+ Zstd(ZstdEncoder<'static, W>),
+}
+// Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely
+pub(crate) mod zip_writer {
+ use super::*;
+ /// ZIP archive generator
+ ///
+ /// Handles the bookkeeping involved in building an archive, and provides an
+ /// API to edit its contents.
+ ///
+ /// ```
+ /// # fn doit() -> zip::result::ZipResult<()>
+ /// # {
+ /// # use zip::ZipWriter;
+ /// use std::io::Write;
+ /// use zip::write::FileOptions;
+ ///
+ /// // We use a buffer here, though you'd normally use a `File`
+ /// let mut buf = [0; 65536];
+ /// let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
+ ///
+ /// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
+ /// zip.start_file("hello_world.txt", options)?;
+ /// zip.write(b"Hello, World!")?;
+ ///
+ /// // Apply the changes you've made.
+ /// // Dropping the `ZipWriter` will have the same effect, but may silently fail
+ /// zip.finish()?;
+ ///
+ /// # Ok(())
+ /// # }
+ /// # doit().unwrap();
+ /// ```
+ pub struct ZipWriter<W: Write + io::Seek> {
+ pub(super) inner: GenericZipWriter<W>,
+ pub(super) files: Vec<ZipFileData>,
+ pub(super) stats: ZipWriterStats,
+ pub(super) writing_to_file: bool,
+ pub(super) writing_to_extra_field: bool,
+ pub(super) writing_to_central_extra_field_only: bool,
+ pub(super) writing_raw: bool,
+ pub(super) comment: Vec<u8>,
+ }
+}
+pub use zip_writer::ZipWriter;
+
+#[derive(Default)]
+struct ZipWriterStats {
+ hasher: Hasher,
+ start: u64,
+ bytes_written: u64,
+}
+
+struct ZipRawValues {
+ crc32: u32,
+ compressed_size: u64,
+ uncompressed_size: u64,
+}
+
+/// Metadata for a file to be written
+#[derive(Copy, Clone)]
+pub struct FileOptions {
+ compression_method: CompressionMethod,
+ compression_level: Option<i32>,
+ last_modified_time: DateTime,
+ permissions: Option<u32>,
+ large_file: bool,
+}
+
+impl FileOptions {
+ /// Set the compression method for the new file
+ ///
+ /// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
+ /// disabled, `CompressionMethod::Stored` becomes the default.
+ #[must_use]
+ pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
+ self.compression_method = method;
+ self
+ }
+
+ /// Set the compression level for the new file
+ ///
+ /// `None` value specifies default compression level.
+ ///
+ /// Range of values depends on compression method:
+ /// * `Deflated`: 0 - 9. Default is 6
+ /// * `Bzip2`: 0 - 9. Default is 6
+ /// * `Zstd`: -7 - 22, with zero being mapped to default level. Default is 3
+ /// * others: only `None` is allowed
+ #[must_use]
+ pub fn compression_level(mut self, level: Option<i32>) -> FileOptions {
+ self.compression_level = level;
+ self
+ }
+
+ /// Set the last modified time
+ ///
+ /// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01
+ /// otherwise
+ #[must_use]
+ pub fn last_modified_time(mut self, mod_time: DateTime) -> FileOptions {
+ self.last_modified_time = mod_time;
+ self
+ }
+
+ /// Set the permissions for the new file.
+ ///
+ /// The format is represented with unix-style permissions.
+ /// The default is `0o644`, which represents `rw-r--r--` for files,
+ /// and `0o755`, which represents `rwxr-xr-x` for directories.
+ ///
+ /// This method only preserves the file permissions bits (via a `& 0o777`) and discards
+ /// higher file mode bits. So it cannot be used to denote an entry as a directory,
+ /// symlink, or other special file type.
+ #[must_use]
+ pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
+ self.permissions = Some(mode & 0o777);
+ self
+ }
+
+ /// Set whether the new file's compressed and uncompressed size is less than 4 GiB.
+ ///
+ /// If set to `false` and the file exceeds the limit, an I/O error is thrown. If set to `true`,
+ /// readers will require ZIP64 support and if the file does not exceed the limit, 20 B are
+ /// wasted. The default is `false`.
+ #[must_use]
+ pub fn large_file(mut self, large: bool) -> FileOptions {
+ self.large_file = large;
+ self
+ }
+}
+
+impl Default for FileOptions {
+ /// Construct a new FileOptions object
+ fn default() -> Self {
+ Self {
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ compression_method: CompressionMethod::Deflated,
+ #[cfg(not(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ )))]
+ compression_method: CompressionMethod::Stored,
+ compression_level: None,
+ #[cfg(feature = "time")]
+ last_modified_time: OffsetDateTime::now_utc().try_into().unwrap_or_default(),
+ #[cfg(not(feature = "time"))]
+ last_modified_time: DateTime::default(),
+ permissions: None,
+ large_file: false,
+ }
+ }
+}
+
+impl<W: Write + io::Seek> Write for ZipWriter<W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ if !self.writing_to_file {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "No file has been started",
+ ));
+ }
+ match self.inner.ref_mut() {
+ Some(ref mut w) => {
+ if self.writing_to_extra_field {
+ self.files.last_mut().unwrap().extra_field.write(buf)
+ } else {
+ let write_result = w.write(buf);
+ if let Ok(count) = write_result {
+ self.stats.update(&buf[0..count]);
+ if self.stats.bytes_written > spec::ZIP64_BYTES_THR
+ && !self.files.last_mut().unwrap().large_file
+ {
+ let _inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Large file option has not been set",
+ ));
+ }
+ }
+ write_result
+ }
+ }
+ None => Err(io::Error::new(
+ io::ErrorKind::BrokenPipe,
+ "ZipWriter was already closed",
+ )),
+ }
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ match self.inner.ref_mut() {
+ Some(ref mut w) => w.flush(),
+ None => Err(io::Error::new(
+ io::ErrorKind::BrokenPipe,
+ "ZipWriter was already closed",
+ )),
+ }
+ }
+}
+
+impl ZipWriterStats {
+ fn update(&mut self, buf: &[u8]) {
+ self.hasher.update(buf);
+ self.bytes_written += buf.len() as u64;
+ }
+}
+
+impl<A: Read + Write + io::Seek> ZipWriter<A> {
+ /// Initializes the archive from an existing ZIP archive, making it ready for append.
+ pub fn new_append(mut readwriter: A) -> ZipResult<ZipWriter<A>> {
+ let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?;
+
+ if footer.disk_number != footer.disk_with_central_directory {
+ return Err(ZipError::UnsupportedArchive(
+ "Support for multi-disk files is not implemented",
+ ));
+ }
+
+ let (archive_offset, directory_start, number_of_files) =
+ ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?;
+
+ if readwriter
+ .seek(io::SeekFrom::Start(directory_start))
+ .is_err()
+ {
+ return Err(ZipError::InvalidArchive(
+ "Could not seek to start of central directory",
+ ));
+ }
+
+ let files = (0..number_of_files)
+ .map(|_| central_header_to_zip_file(&mut readwriter, archive_offset))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let _ = readwriter.seek(io::SeekFrom::Start(directory_start)); // seek directory_start to overwrite it
+
+ Ok(ZipWriter {
+ inner: GenericZipWriter::Storer(readwriter),
+ files,
+ stats: Default::default(),
+ writing_to_file: false,
+ writing_to_extra_field: false,
+ writing_to_central_extra_field_only: false,
+ comment: footer.zip_file_comment,
+ writing_raw: true, // avoid recomputing the last file's header
+ })
+ }
+}
+
+impl<W: Write + io::Seek> ZipWriter<W> {
+ /// Initializes the archive.
+ ///
+ /// Before writing to this object, the [`ZipWriter::start_file`] function should be called.
+ pub fn new(inner: W) -> ZipWriter<W> {
+ ZipWriter {
+ inner: GenericZipWriter::Storer(inner),
+ files: Vec::new(),
+ stats: Default::default(),
+ writing_to_file: false,
+ writing_to_extra_field: false,
+ writing_to_central_extra_field_only: false,
+ writing_raw: false,
+ comment: Vec::new(),
+ }
+ }
+
+ /// Set ZIP archive comment.
+ pub fn set_comment<S>(&mut self, comment: S)
+ where
+ S: Into<String>,
+ {
+ self.set_raw_comment(comment.into().into())
+ }
+
+ /// Set ZIP archive comment.
+ ///
+ /// This sets the raw bytes of the comment. The comment
+ /// is typically expected to be encoded in UTF-8
+ pub fn set_raw_comment(&mut self, comment: Vec<u8>) {
+ self.comment = comment;
+ }
+
+ /// Start a new file for with the requested options.
+ fn start_entry<S>(
+ &mut self,
+ name: S,
+ options: FileOptions,
+ raw_values: Option<ZipRawValues>,
+ ) -> ZipResult<()>
+ where
+ S: Into<String>,
+ {
+ self.finish_file()?;
+
+ let raw_values = raw_values.unwrap_or(ZipRawValues {
+ crc32: 0,
+ compressed_size: 0,
+ uncompressed_size: 0,
+ });
+
+ {
+ let writer = self.inner.get_plain();
+ let header_start = writer.stream_position()?;
+
+ let permissions = options.permissions.unwrap_or(0o100644);
+ let mut file = ZipFileData {
+ system: System::Unix,
+ version_made_by: DEFAULT_VERSION,
+ encrypted: false,
+ using_data_descriptor: false,
+ compression_method: options.compression_method,
+ compression_level: options.compression_level,
+ last_modified_time: options.last_modified_time,
+ crc32: raw_values.crc32,
+ compressed_size: raw_values.compressed_size,
+ uncompressed_size: raw_values.uncompressed_size,
+ file_name: name.into(),
+ file_name_raw: Vec::new(), // Never used for saving
+ extra_field: Vec::new(),
+ file_comment: String::new(),
+ header_start,
+ data_start: AtomicU64::new(0),
+ central_header_start: 0,
+ external_attributes: permissions << 16,
+ large_file: options.large_file,
+ aes_mode: None,
+ };
+ write_local_file_header(writer, &file)?;
+
+ let header_end = writer.stream_position()?;
+ self.stats.start = header_end;
+ *file.data_start.get_mut() = header_end;
+
+ self.stats.bytes_written = 0;
+ self.stats.hasher = Hasher::new();
+
+ self.files.push(file);
+ }
+
+ Ok(())
+ }
+
+ fn finish_file(&mut self) -> ZipResult<()> {
+ if self.writing_to_extra_field {
+ // Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
+ self.end_extra_data()?;
+ }
+ self.inner.switch_to(CompressionMethod::Stored, None)?;
+ let writer = self.inner.get_plain();
+
+ if !self.writing_raw {
+ let file = match self.files.last_mut() {
+ None => return Ok(()),
+ Some(f) => f,
+ };
+ file.crc32 = self.stats.hasher.clone().finalize();
+ file.uncompressed_size = self.stats.bytes_written;
+
+ let file_end = writer.stream_position()?;
+ file.compressed_size = file_end - self.stats.start;
+
+ update_local_file_header(writer, file)?;
+ writer.seek(io::SeekFrom::Start(file_end))?;
+ }
+
+ self.writing_to_file = false;
+ self.writing_raw = false;
+ Ok(())
+ }
+
+ /// Create a file in the archive and start writing its' contents.
+ ///
+ /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
+ pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
+ where
+ S: Into<String>,
+ {
+ if options.permissions.is_none() {
+ options.permissions = Some(0o644);
+ }
+ *options.permissions.as_mut().unwrap() |= 0o100000;
+ self.start_entry(name, options, None)?;
+ self.inner
+ .switch_to(options.compression_method, options.compression_level)?;
+ self.writing_to_file = true;
+ Ok(())
+ }
+
+ /// Starts a file, taking a Path as argument.
+ ///
+ /// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
+ /// Components, such as a starting '/' or '..' and '.'.
+ #[deprecated(
+ since = "0.5.7",
+ note = "by stripping `..`s from the path, the meaning of paths can change. Use `start_file` instead."
+ )]
+ pub fn start_file_from_path(
+ &mut self,
+ path: &std::path::Path,
+ options: FileOptions,
+ ) -> ZipResult<()> {
+ self.start_file(path_to_string(path), options)
+ }
+
+ /// Create an aligned file in the archive and start writing its' contents.
+ ///
+ /// Returns the number of padding bytes required to align the file.
+ ///
+ /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
+ pub fn start_file_aligned<S>(
+ &mut self,
+ name: S,
+ options: FileOptions,
+ align: u16,
+ ) -> Result<u64, ZipError>
+ where
+ S: Into<String>,
+ {
+ let data_start = self.start_file_with_extra_data(name, options)?;
+ let align = align as u64;
+ if align > 1 && data_start % align != 0 {
+ let pad_length = (align - (data_start + 4) % align) % align;
+ let pad = vec![0; pad_length as usize];
+ self.write_all(b"za").map_err(ZipError::from)?; // 0x617a
+ self.write_u16::<LittleEndian>(pad.len() as u16)
+ .map_err(ZipError::from)?;
+ self.write_all(&pad).map_err(ZipError::from)?;
+ assert_eq!(self.end_local_start_central_extra_data()? % align, 0);
+ }
+ let extra_data_end = self.end_extra_data()?;
+ Ok(extra_data_end - data_start)
+ }
+
+ /// Create a file in the archive and start writing its extra data first.
+ ///
+ /// Finish writing extra data and start writing file data with [`ZipWriter::end_extra_data`].
+ /// Optionally, distinguish local from central extra data with
+ /// [`ZipWriter::end_local_start_central_extra_data`].
+ ///
+ /// Returns the preliminary starting offset of the file data without any extra data allowing to
+ /// align the file data by calculating a pad length to be prepended as part of the extra data.
+ ///
+ /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
+ ///
+ /// ```
+ /// use byteorder::{LittleEndian, WriteBytesExt};
+ /// use zip::{ZipArchive, ZipWriter, result::ZipResult};
+ /// use zip::{write::FileOptions, CompressionMethod};
+ /// use std::io::{Write, Cursor};
+ ///
+ /// # fn main() -> ZipResult<()> {
+ /// let mut archive = Cursor::new(Vec::new());
+ ///
+ /// {
+ /// let mut zip = ZipWriter::new(&mut archive);
+ /// let options = FileOptions::default()
+ /// .compression_method(CompressionMethod::Stored);
+ ///
+ /// zip.start_file_with_extra_data("identical_extra_data.txt", options)?;
+ /// let extra_data = b"local and central extra data";
+ /// zip.write_u16::<LittleEndian>(0xbeef)?;
+ /// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
+ /// zip.write_all(extra_data)?;
+ /// zip.end_extra_data()?;
+ /// zip.write_all(b"file data")?;
+ ///
+ /// let data_start = zip.start_file_with_extra_data("different_extra_data.txt", options)?;
+ /// let extra_data = b"local extra data";
+ /// zip.write_u16::<LittleEndian>(0xbeef)?;
+ /// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
+ /// zip.write_all(extra_data)?;
+ /// let data_start = data_start as usize + 4 + extra_data.len() + 4;
+ /// let align = 64;
+ /// let pad_length = (align - data_start % align) % align;
+ /// assert_eq!(pad_length, 19);
+ /// zip.write_u16::<LittleEndian>(0xdead)?;
+ /// zip.write_u16::<LittleEndian>(pad_length as u16)?;
+ /// zip.write_all(&vec![0; pad_length])?;
+ /// let data_start = zip.end_local_start_central_extra_data()?;
+ /// assert_eq!(data_start as usize % align, 0);
+ /// let extra_data = b"central extra data";
+ /// zip.write_u16::<LittleEndian>(0xbeef)?;
+ /// zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
+ /// zip.write_all(extra_data)?;
+ /// zip.end_extra_data()?;
+ /// zip.write_all(b"file data")?;
+ ///
+ /// zip.finish()?;
+ /// }
+ ///
+ /// let mut zip = ZipArchive::new(archive)?;
+ /// assert_eq!(&zip.by_index(0)?.extra_data()[4..], b"local and central extra data");
+ /// assert_eq!(&zip.by_index(1)?.extra_data()[4..], b"central extra data");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn start_file_with_extra_data<S>(
+ &mut self,
+ name: S,
+ mut options: FileOptions,
+ ) -> ZipResult<u64>
+ where
+ S: Into<String>,
+ {
+ if options.permissions.is_none() {
+ options.permissions = Some(0o644);
+ }
+ *options.permissions.as_mut().unwrap() |= 0o100000;
+ self.start_entry(name, options, None)?;
+ self.writing_to_file = true;
+ self.writing_to_extra_field = true;
+ Ok(self.files.last().unwrap().data_start.load())
+ }
+
+ /// End local and start central extra data. Requires [`ZipWriter::start_file_with_extra_data`].
+ ///
+ /// Returns the final starting offset of the file data.
+ pub fn end_local_start_central_extra_data(&mut self) -> ZipResult<u64> {
+ let data_start = self.end_extra_data()?;
+ self.files.last_mut().unwrap().extra_field.clear();
+ self.writing_to_extra_field = true;
+ self.writing_to_central_extra_field_only = true;
+ Ok(data_start)
+ }
+
+ /// End extra data and start file data. Requires [`ZipWriter::start_file_with_extra_data`].
+ ///
+ /// Returns the final starting offset of the file data.
+ pub fn end_extra_data(&mut self) -> ZipResult<u64> {
+ // Require `start_file_with_extra_data()`. Ensures `file` is some.
+ if !self.writing_to_extra_field {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ "Not writing to extra field",
+ )));
+ }
+ let file = self.files.last_mut().unwrap();
+
+ validate_extra_data(file)?;
+
+ let data_start = file.data_start.get_mut();
+
+ if !self.writing_to_central_extra_field_only {
+ let writer = self.inner.get_plain();
+
+ // Append extra data to local file header and keep it for central file header.
+ writer.write_all(&file.extra_field)?;
+
+ // Update final `data_start`.
+ let header_end = *data_start + file.extra_field.len() as u64;
+ self.stats.start = header_end;
+ *data_start = header_end;
+
+ // Update extra field length in local file header.
+ let extra_field_length =
+ if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
+ writer.seek(io::SeekFrom::Start(file.header_start + 28))?;
+ writer.write_u16::<LittleEndian>(extra_field_length)?;
+ writer.seek(io::SeekFrom::Start(header_end))?;
+
+ self.inner
+ .switch_to(file.compression_method, file.compression_level)?;
+ }
+
+ self.writing_to_extra_field = false;
+ self.writing_to_central_extra_field_only = false;
+ Ok(*data_start)
+ }
+
+ /// Add a new file using the already compressed data from a ZIP file being read and renames it, this
+ /// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again.
+ /// Any `ZipFile` metadata is copied and not checked, for example the file CRC.
+
+ /// ```no_run
+ /// use std::fs::File;
+ /// use std::io::{Read, Seek, Write};
+ /// use zip::{ZipArchive, ZipWriter};
+ ///
+ /// fn copy_rename<R, W>(
+ /// src: &mut ZipArchive<R>,
+ /// dst: &mut ZipWriter<W>,
+ /// ) -> zip::result::ZipResult<()>
+ /// where
+ /// R: Read + Seek,
+ /// W: Write + Seek,
+ /// {
+ /// // Retrieve file entry by name
+ /// let file = src.by_name("src_file.txt")?;
+ ///
+ /// // Copy and rename the previously obtained file entry to the destination zip archive
+ /// dst.raw_copy_file_rename(file, "new_name.txt")?;
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn raw_copy_file_rename<S>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
+ where
+ S: Into<String>,
+ {
+ let mut options = FileOptions::default()
+ .large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
+ .last_modified_time(file.last_modified())
+ .compression_method(file.compression());
+ if let Some(perms) = file.unix_mode() {
+ options = options.unix_permissions(perms);
+ }
+
+ let raw_values = ZipRawValues {
+ crc32: file.crc32(),
+ compressed_size: file.compressed_size(),
+ uncompressed_size: file.size(),
+ };
+
+ self.start_entry(name, options, Some(raw_values))?;
+ self.writing_to_file = true;
+ self.writing_raw = true;
+
+ io::copy(file.get_raw_reader(), self)?;
+
+ Ok(())
+ }
+
+ /// Add a new file using the already compressed data from a ZIP file being read, this allows faster
+ /// copies of the `ZipFile` since there is no need to decompress and compress it again. Any `ZipFile`
+ /// metadata is copied and not checked, for example the file CRC.
+ ///
+ /// ```no_run
+ /// use std::fs::File;
+ /// use std::io::{Read, Seek, Write};
+ /// use zip::{ZipArchive, ZipWriter};
+ ///
+ /// fn copy<R, W>(src: &mut ZipArchive<R>, dst: &mut ZipWriter<W>) -> zip::result::ZipResult<()>
+ /// where
+ /// R: Read + Seek,
+ /// W: Write + Seek,
+ /// {
+ /// // Retrieve file entry by name
+ /// let file = src.by_name("src_file.txt")?;
+ ///
+ /// // Copy the previously obtained file entry to the destination zip archive
+ /// dst.raw_copy_file(file)?;
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn raw_copy_file(&mut self, file: ZipFile) -> ZipResult<()> {
+ let name = file.name().to_owned();
+ self.raw_copy_file_rename(file, name)
+ }
+
+ /// Add a directory entry.
+ ///
+ /// You can't write data to the file afterwards.
+ pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
+ where
+ S: Into<String>,
+ {
+ if options.permissions.is_none() {
+ options.permissions = Some(0o755);
+ }
+ *options.permissions.as_mut().unwrap() |= 0o40000;
+ options.compression_method = CompressionMethod::Stored;
+
+ let name_as_string = name.into();
+ // Append a slash to the filename if it does not end with it.
+ let name_with_slash = match name_as_string.chars().last() {
+ Some('/') | Some('\\') => name_as_string,
+ _ => name_as_string + "/",
+ };
+
+ self.start_entry(name_with_slash, options, None)?;
+ self.writing_to_file = false;
+ Ok(())
+ }
+
+ /// Add a directory entry, taking a Path as argument.
+ ///
+ /// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
+ /// Components, such as a starting '/' or '..' and '.'.
+ #[deprecated(
+ since = "0.5.7",
+ note = "by stripping `..`s from the path, the meaning of paths can change. Use `add_directory` instead."
+ )]
+ pub fn add_directory_from_path(
+ &mut self,
+ path: &std::path::Path,
+ options: FileOptions,
+ ) -> ZipResult<()> {
+ self.add_directory(path_to_string(path), options)
+ }
+
+ /// Finish the last file and write all other zip-structures
+ ///
+ /// This will return the writer, but one should normally not append any data to the end of the file.
+ /// Note that the zipfile will also be finished on drop.
+ pub fn finish(&mut self) -> ZipResult<W> {
+ self.finalize()?;
+ let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
+ Ok(inner.unwrap())
+ }
+
+ /// Add a symlink entry.
+ ///
+ /// The zip archive will contain an entry for path `name` which is a symlink to `target`.
+ ///
+ /// No validation or normalization of the paths is performed. For best results,
+ /// callers should normalize `\` to `/` and ensure symlinks are relative to other
+ /// paths within the zip archive.
+ ///
+ /// WARNING: not all zip implementations preserve symlinks on extract. Some zip
+ /// implementations may materialize a symlink as a regular file, possibly with the
+ /// content incorrectly set to the symlink target. For maximum portability, consider
+ /// storing a regular file instead.
+ pub fn add_symlink<N, T>(
+ &mut self,
+ name: N,
+ target: T,
+ mut options: FileOptions,
+ ) -> ZipResult<()>
+ where
+ N: Into<String>,
+ T: Into<String>,
+ {
+ if options.permissions.is_none() {
+ options.permissions = Some(0o777);
+ }
+ *options.permissions.as_mut().unwrap() |= 0o120000;
+ // The symlink target is stored as file content. And compressing the target path
+ // likely wastes space. So always store.
+ options.compression_method = CompressionMethod::Stored;
+
+ self.start_entry(name, options, None)?;
+ self.writing_to_file = true;
+ self.write_all(target.into().as_bytes())?;
+ self.writing_to_file = false;
+
+ Ok(())
+ }
+
+ fn finalize(&mut self) -> ZipResult<()> {
+ self.finish_file()?;
+
+ {
+ let writer = self.inner.get_plain();
+
+ let central_start = writer.stream_position()?;
+ for file in self.files.iter() {
+ write_central_directory_header(writer, file)?;
+ }
+ let central_size = writer.stream_position()? - central_start;
+
+ if self.files.len() > spec::ZIP64_ENTRY_THR
+ || central_size.max(central_start) > spec::ZIP64_BYTES_THR
+ {
+ let zip64_footer = spec::Zip64CentralDirectoryEnd {
+ version_made_by: DEFAULT_VERSION as u16,
+ version_needed_to_extract: DEFAULT_VERSION as u16,
+ disk_number: 0,
+ disk_with_central_directory: 0,
+ number_of_files_on_this_disk: self.files.len() as u64,
+ number_of_files: self.files.len() as u64,
+ central_directory_size: central_size,
+ central_directory_offset: central_start,
+ };
+
+ zip64_footer.write(writer)?;
+
+ let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
+ disk_with_central_directory: 0,
+ end_of_central_directory_offset: central_start + central_size,
+ number_of_disks: 1,
+ };
+
+ zip64_footer.write(writer)?;
+ }
+
+ let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
+ let footer = spec::CentralDirectoryEnd {
+ disk_number: 0,
+ disk_with_central_directory: 0,
+ zip_file_comment: self.comment.clone(),
+ number_of_files_on_this_disk: number_of_files,
+ number_of_files,
+ central_directory_size: central_size.min(spec::ZIP64_BYTES_THR) as u32,
+ central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
+ };
+
+ footer.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl<W: Write + io::Seek> Drop for ZipWriter<W> {
+ fn drop(&mut self) {
+ if !self.inner.is_closed() {
+ if let Err(e) = self.finalize() {
+ let _ = write!(io::stderr(), "ZipWriter drop failed: {e:?}");
+ }
+ }
+ }
+}
+
+impl<W: Write + io::Seek> GenericZipWriter<W> {
+ fn switch_to(
+ &mut self,
+ compression: CompressionMethod,
+ compression_level: Option<i32>,
+ ) -> ZipResult<()> {
+ match self.current_compression() {
+ Some(method) if method == compression => return Ok(()),
+ None => {
+ return Err(io::Error::new(
+ io::ErrorKind::BrokenPipe,
+ "ZipWriter was already closed",
+ )
+ .into())
+ }
+ _ => {}
+ }
+
+ let bare = match mem::replace(self, GenericZipWriter::Closed) {
+ GenericZipWriter::Storer(w) => w,
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ GenericZipWriter::Deflater(w) => w.finish()?,
+ #[cfg(feature = "bzip2")]
+ GenericZipWriter::Bzip2(w) => w.finish()?,
+ #[cfg(feature = "zstd")]
+ GenericZipWriter::Zstd(w) => w.finish()?,
+ GenericZipWriter::Closed => {
+ return Err(io::Error::new(
+ io::ErrorKind::BrokenPipe,
+ "ZipWriter was already closed",
+ )
+ .into())
+ }
+ };
+
+ *self = {
+ #[allow(deprecated)]
+ match compression {
+ CompressionMethod::Stored => {
+ if compression_level.is_some() {
+ return Err(ZipError::UnsupportedArchive(
+ "Unsupported compression level",
+ ));
+ }
+
+ GenericZipWriter::Storer(bare)
+ }
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(
+ bare,
+ flate2::Compression::new(
+ clamp_opt(
+ compression_level
+ .unwrap_or(flate2::Compression::default().level() as i32),
+ deflate_compression_level_range(),
+ )
+ .ok_or(ZipError::UnsupportedArchive(
+ "Unsupported compression level",
+ ))? as u32,
+ ),
+ )),
+ #[cfg(feature = "bzip2")]
+ CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(
+ bare,
+ bzip2::Compression::new(
+ clamp_opt(
+ compression_level
+ .unwrap_or(bzip2::Compression::default().level() as i32),
+ bzip2_compression_level_range(),
+ )
+ .ok_or(ZipError::UnsupportedArchive(
+ "Unsupported compression level",
+ ))? as u32,
+ ),
+ )),
+ CompressionMethod::AES => {
+ return Err(ZipError::UnsupportedArchive(
+ "AES compression is not supported for writing",
+ ))
+ }
+ #[cfg(feature = "zstd")]
+ CompressionMethod::Zstd => GenericZipWriter::Zstd(
+ ZstdEncoder::new(
+ bare,
+ clamp_opt(
+ compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL),
+ zstd::compression_level_range(),
+ )
+ .ok_or(ZipError::UnsupportedArchive(
+ "Unsupported compression level",
+ ))?,
+ )
+ .unwrap(),
+ ),
+ CompressionMethod::Unsupported(..) => {
+ return Err(ZipError::UnsupportedArchive("Unsupported compression"))
+ }
+ }
+ };
+
+ Ok(())
+ }
+
+ fn ref_mut(&mut self) -> Option<&mut dyn Write> {
+ match *self {
+ GenericZipWriter::Storer(ref mut w) => Some(w as &mut dyn Write),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ GenericZipWriter::Deflater(ref mut w) => Some(w as &mut dyn Write),
+ #[cfg(feature = "bzip2")]
+ GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut dyn Write),
+ #[cfg(feature = "zstd")]
+ GenericZipWriter::Zstd(ref mut w) => Some(w as &mut dyn Write),
+ GenericZipWriter::Closed => None,
+ }
+ }
+
+ fn is_closed(&self) -> bool {
+ matches!(*self, GenericZipWriter::Closed)
+ }
+
+ fn get_plain(&mut self) -> &mut W {
+ match *self {
+ GenericZipWriter::Storer(ref mut w) => w,
+ _ => panic!("Should have switched to stored beforehand"),
+ }
+ }
+
+ fn current_compression(&self) -> Option<CompressionMethod> {
+ match *self {
+ GenericZipWriter::Storer(..) => Some(CompressionMethod::Stored),
+ #[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+ ))]
+ GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated),
+ #[cfg(feature = "bzip2")]
+ GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2),
+ #[cfg(feature = "zstd")]
+ GenericZipWriter::Zstd(..) => Some(CompressionMethod::Zstd),
+ GenericZipWriter::Closed => None,
+ }
+ }
+
+ fn unwrap(self) -> W {
+ match self {
+ GenericZipWriter::Storer(w) => w,
+ _ => panic!("Should have switched to stored beforehand"),
+ }
+ }
+}
+
+#[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib"
+))]
+fn deflate_compression_level_range() -> std::ops::RangeInclusive<i32> {
+ let min = flate2::Compression::none().level() as i32;
+ let max = flate2::Compression::best().level() as i32;
+ min..=max
+}
+
+#[cfg(feature = "bzip2")]
+fn bzip2_compression_level_range() -> std::ops::RangeInclusive<i32> {
+ let min = bzip2::Compression::none().level() as i32;
+ let max = bzip2::Compression::best().level() as i32;
+ min..=max
+}
+
+#[cfg(any(
+ feature = "deflate",
+ feature = "deflate-miniz",
+ feature = "deflate-zlib",
+ feature = "bzip2",
+ feature = "zstd"
+))]
+fn clamp_opt<T: Ord + Copy>(value: T, range: std::ops::RangeInclusive<T>) -> Option<T> {
+ if range.contains(&value) {
+ Some(value)
+ } else {
+ None
+ }
+}
+
+fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
+ // local file header signature
+ writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
+ // version needed to extract
+ writer.write_u16::<LittleEndian>(file.version_needed())?;
+ // general purpose bit flag
+ let flag = if !file.file_name.is_ascii() {
+ 1u16 << 11
+ } else {
+ 0
+ };
+ writer.write_u16::<LittleEndian>(flag)?;
+ // Compression method
+ #[allow(deprecated)]
+ writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
+ // last mod file time and last mod file date
+ writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
+ writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
+ // crc-32
+ writer.write_u32::<LittleEndian>(file.crc32)?;
+ // compressed size and uncompressed size
+ if file.large_file {
+ writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
+ writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
+ } else {
+ writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
+ writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
+ }
+ // file name length
+ writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
+ // extra field length
+ let extra_field_length = if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
+ writer.write_u16::<LittleEndian>(extra_field_length)?;
+ // file name
+ writer.write_all(file.file_name.as_bytes())?;
+ // zip64 extra field
+ if file.large_file {
+ write_local_zip64_extra_field(writer, file)?;
+ }
+
+ Ok(())
+}
+
+fn update_local_file_header<T: Write + io::Seek>(
+ writer: &mut T,
+ file: &ZipFileData,
+) -> ZipResult<()> {
+ const CRC32_OFFSET: u64 = 14;
+ writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
+ writer.write_u32::<LittleEndian>(file.crc32)?;
+ if file.large_file {
+ update_local_zip64_extra_field(writer, file)?;
+ } else {
+ // check compressed size as well as it can also be slightly larger than uncompressed size
+ if file.compressed_size > spec::ZIP64_BYTES_THR {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ "Large file option has not been set",
+ )));
+ }
+ writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
+ // uncompressed size is already checked on write to catch it as soon as possible
+ writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
+ }
+ Ok(())
+}
+
+fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
+ // buffer zip64 extra field to determine its variable length
+ let mut zip64_extra_field = [0; 28];
+ let zip64_extra_field_length =
+ write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?;
+
+ // central file header signature
+ writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
+ // version made by
+ let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16);
+ writer.write_u16::<LittleEndian>(version_made_by)?;
+ // version needed to extract
+ writer.write_u16::<LittleEndian>(file.version_needed())?;
+ // general puprose bit flag
+ let flag = if !file.file_name.is_ascii() {
+ 1u16 << 11
+ } else {
+ 0
+ };
+ writer.write_u16::<LittleEndian>(flag)?;
+ // compression method
+ #[allow(deprecated)]
+ writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
+ // last mod file time + date
+ writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
+ writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
+ // crc-32
+ writer.write_u32::<LittleEndian>(file.crc32)?;
+ // compressed size
+ writer.write_u32::<LittleEndian>(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
+ // uncompressed size
+ writer.write_u32::<LittleEndian>(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
+ // file name length
+ writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
+ // extra field length
+ writer.write_u16::<LittleEndian>(zip64_extra_field_length + file.extra_field.len() as u16)?;
+ // file comment length
+ writer.write_u16::<LittleEndian>(0)?;
+ // disk number start
+ writer.write_u16::<LittleEndian>(0)?;
+ // internal file attribytes
+ writer.write_u16::<LittleEndian>(0)?;
+ // external file attributes
+ writer.write_u32::<LittleEndian>(file.external_attributes)?;
+ // relative offset of local header
+ writer.write_u32::<LittleEndian>(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?;
+ // file name
+ writer.write_all(file.file_name.as_bytes())?;
+ // zip64 extra field
+ writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?;
+ // extra field
+ writer.write_all(&file.extra_field)?;
+ // file comment
+ // <none>
+
+ Ok(())
+}
+
+fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
+ let mut data = file.extra_field.as_slice();
+
+ if data.len() > spec::ZIP64_ENTRY_THR {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Extra data exceeds extra field",
+ )));
+ }
+
+ while !data.is_empty() {
+ let left = data.len();
+ if left < 4 {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ "Incomplete extra data header",
+ )));
+ }
+ let kind = data.read_u16::<LittleEndian>()?;
+ let size = data.read_u16::<LittleEndian>()? as usize;
+ let left = left - 4;
+
+ if kind == 0x0001 {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ "No custom ZIP64 extra data allowed",
+ )));
+ }
+
+ #[cfg(not(feature = "unreserved"))]
+ {
+ if kind <= 31 || EXTRA_FIELD_MAPPING.iter().any(|&mapped| mapped == kind) {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ format!(
+ "Extra data header ID {kind:#06} requires crate feature \"unreserved\"",
+ ),
+ )));
+ }
+ }
+
+ if size > left {
+ return Err(ZipError::Io(io::Error::new(
+ io::ErrorKind::Other,
+ "Extra data size exceeds extra field",
+ )));
+ }
+
+ data = &data[size..];
+ }
+
+ Ok(())
+}
+
+fn write_local_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
+ // This entry in the Local header MUST include BOTH original
+ // and compressed file size fields.
+ writer.write_u16::<LittleEndian>(0x0001)?;
+ writer.write_u16::<LittleEndian>(16)?;
+ writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
+ writer.write_u64::<LittleEndian>(file.compressed_size)?;
+ // Excluded fields:
+ // u32: disk start number
+ Ok(())
+}
+
+fn update_local_zip64_extra_field<T: Write + io::Seek>(
+ writer: &mut T,
+ file: &ZipFileData,
+) -> ZipResult<()> {
+ let zip64_extra_field = file.header_start + 30 + file.file_name.as_bytes().len() as u64;
+ writer.seek(io::SeekFrom::Start(zip64_extra_field + 4))?;
+ writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
+ writer.write_u64::<LittleEndian>(file.compressed_size)?;
+ // Excluded fields:
+ // u32: disk start number
+ Ok(())
+}
+
+fn write_central_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<u16> {
+ // The order of the fields in the zip64 extended
+ // information record is fixed, but the fields MUST
+ // only appear if the corresponding Local or Central
+ // directory record field is set to 0xFFFF or 0xFFFFFFFF.
+ let mut size = 0;
+ let uncompressed_size = file.uncompressed_size > spec::ZIP64_BYTES_THR;
+ let compressed_size = file.compressed_size > spec::ZIP64_BYTES_THR;
+ let header_start = file.header_start > spec::ZIP64_BYTES_THR;
+ if uncompressed_size {
+ size += 8;
+ }
+ if compressed_size {
+ size += 8;
+ }
+ if header_start {
+ size += 8;
+ }
+ if size > 0 {
+ writer.write_u16::<LittleEndian>(0x0001)?;
+ writer.write_u16::<LittleEndian>(size)?;
+ size += 4;
+
+ if uncompressed_size {
+ writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
+ }
+ if compressed_size {
+ writer.write_u64::<LittleEndian>(file.compressed_size)?;
+ }
+ if header_start {
+ writer.write_u64::<LittleEndian>(file.header_start)?;
+ }
+ // Excluded fields:
+ // u32: disk start number
+ }
+ Ok(size)
+}
+
+fn path_to_string(path: &std::path::Path) -> String {
+ let mut path_str = String::new();
+ for component in path.components() {
+ if let std::path::Component::Normal(os_str) = component {
+ if !path_str.is_empty() {
+ path_str.push('/');
+ }
+ path_str.push_str(&os_str.to_string_lossy());
+ }
+ }
+ path_str
+}
+
+#[cfg(test)]
+mod test {
+ use super::{FileOptions, ZipWriter};
+ use crate::compression::CompressionMethod;
+ use crate::types::DateTime;
+ use std::io;
+ use std::io::Write;
+
+ #[test]
+ fn write_empty_zip() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer.set_comment("ZIP");
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 25);
+ assert_eq!(
+ *result.get_ref(),
+ [80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80]
+ );
+ }
+
+ #[test]
+ fn unix_permissions_bitmask() {
+ // unix_permissions() throws away upper bits.
+ let options = FileOptions::default().unix_permissions(0o120777);
+ assert_eq!(options.permissions, Some(0o777));
+ }
+
+ #[test]
+ fn write_zip_dir() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer
+ .add_directory(
+ "test",
+ FileOptions::default().last_modified_time(
+ DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
+ ),
+ )
+ .unwrap();
+ assert!(writer
+ .write(b"writing to a directory is not allowed, and will not write any data")
+ .is_err());
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 108);
+ assert_eq!(
+ *result.get_ref(),
+ &[
+ 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0,
+ 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
+ 1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 0, 0,
+ ] as &[u8]
+ );
+ }
+
+ #[test]
+ fn write_symlink_simple() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer
+ .add_symlink(
+ "name",
+ "target",
+ FileOptions::default().last_modified_time(
+ DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
+ ),
+ )
+ .unwrap();
+ assert!(writer
+ .write(b"writing to a symlink is not allowed and will not write any data")
+ .is_err());
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 112);
+ assert_eq!(
+ *result.get_ref(),
+ &[
+ 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
+ 6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
+ 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
+ 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
+ ] as &[u8],
+ );
+ }
+
+ #[test]
+ fn write_symlink_wonky_paths() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ writer
+ .add_symlink(
+ "directory\\link",
+ "/absolute/symlink\\with\\mixed/slashes",
+ FileOptions::default().last_modified_time(
+ DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
+ ),
+ )
+ .unwrap();
+ assert!(writer
+ .write(b"writing to a symlink is not allowed and will not write any data")
+ .is_err());
+ let result = writer.finish().unwrap();
+ assert_eq!(result.get_ref().len(), 162);
+ assert_eq!(
+ *result.get_ref(),
+ &[
+ 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
+ 36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
+ 110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
+ 110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
+ 115, 104, 101, 115, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
+ 41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
+ 161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
+ 107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
+ ] as &[u8],
+ );
+ }
+
+ #[test]
+ fn write_mimetype_zip() {
+ let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
+ let options = FileOptions {
+ compression_method: CompressionMethod::Stored,
+ compression_level: None,
+ last_modified_time: DateTime::default(),
+ permissions: Some(33188),
+ large_file: false,
+ };
+ writer.start_file("mimetype", options).unwrap();
+ writer
+ .write_all(b"application/vnd.oasis.opendocument.text")
+ .unwrap();
+ let result = writer.finish().unwrap();
+
+ assert_eq!(result.get_ref().len(), 153);
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
+ assert_eq!(result.get_ref(), &v);
+ }
+
+ #[test]
+ fn path_to_string() {
+ let mut path = std::path::PathBuf::new();
+ #[cfg(windows)]
+ path.push(r"C:\");
+ #[cfg(unix)]
+ path.push("/");
+ path.push("windows");
+ path.push("..");
+ path.push(".");
+ path.push("system32");
+ let path_str = super::path_to_string(&path);
+ assert_eq!(path_str, "windows/system32");
+ }
+}
+
+#[cfg(not(feature = "unreserved"))]
+const EXTRA_FIELD_MAPPING: [u16; 49] = [
+ 0x0001, 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016,
+ 0x0017, 0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605,
+ 0x2705, 0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356,
+ 0x5455, 0x554e, 0x5855, 0x6375, 0x6542, 0x7075, 0x756e, 0x7855, 0xa11e, 0xa220, 0xfd4a, 0x9901,
+ 0x9902,
+];
diff --git a/third_party/rust/zip/src/zipcrypto.rs b/third_party/rust/zip/src/zipcrypto.rs
new file mode 100644
index 0000000000..91d403951b
--- /dev/null
+++ b/third_party/rust/zip/src/zipcrypto.rs
@@ -0,0 +1,184 @@
+//! Implementation of the ZipCrypto algorithm
+//!
+//! The following paper was used to implement the ZipCrypto algorithm:
+//! [https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf](https://courses.cs.ut.ee/MTAT.07.022/2015_fall/uploads/Main/dmitri-report-f15-16.pdf)
+
+use std::num::Wrapping;
+
+/// A container to hold the current key state
+struct ZipCryptoKeys {
+ key_0: Wrapping<u32>,
+ key_1: Wrapping<u32>,
+ key_2: Wrapping<u32>,
+}
+
+impl ZipCryptoKeys {
+ fn new() -> ZipCryptoKeys {
+ ZipCryptoKeys {
+ key_0: Wrapping(0x12345678),
+ key_1: Wrapping(0x23456789),
+ key_2: Wrapping(0x34567890),
+ }
+ }
+
+ fn update(&mut self, input: u8) {
+ self.key_0 = ZipCryptoKeys::crc32(self.key_0, input);
+ self.key_1 =
+ (self.key_1 + (self.key_0 & Wrapping(0xff))) * Wrapping(0x08088405) + Wrapping(1);
+ self.key_2 = ZipCryptoKeys::crc32(self.key_2, (self.key_1 >> 24).0 as u8);
+ }
+
+ fn stream_byte(&mut self) -> u8 {
+ let temp: Wrapping<u16> = Wrapping(self.key_2.0 as u16) | Wrapping(3);
+ ((temp * (temp ^ Wrapping(1))) >> 8).0 as u8
+ }
+
+ fn decrypt_byte(&mut self, cipher_byte: u8) -> u8 {
+ let plain_byte: u8 = self.stream_byte() ^ cipher_byte;
+ self.update(plain_byte);
+ plain_byte
+ }
+
+ #[allow(dead_code)]
+ fn encrypt_byte(&mut self, plain_byte: u8) -> u8 {
+ let cipher_byte: u8 = self.stream_byte() ^ plain_byte;
+ self.update(plain_byte);
+ cipher_byte
+ }
+
+ fn crc32(crc: Wrapping<u32>, input: u8) -> Wrapping<u32> {
+ (crc >> 8) ^ Wrapping(CRCTABLE[((crc & Wrapping(0xff)).0 as u8 ^ input) as usize])
+ }
+}
+
+/// A ZipCrypto reader with unverified password
+pub struct ZipCryptoReader<R> {
+ file: R,
+ keys: ZipCryptoKeys,
+}
+
+pub enum ZipCryptoValidator {
+ PkzipCrc32(u32),
+ InfoZipMsdosTime(u16),
+}
+
+impl<R: std::io::Read> ZipCryptoReader<R> {
+ /// Note: The password is `&[u8]` and not `&str` because the
+ /// [zip specification](https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.3.TXT)
+ /// does not specify password encoding (see function `update_keys` in the specification).
+ /// Therefore, if `&str` was used, the password would be UTF-8 and it
+ /// would be impossible to decrypt files that were encrypted with a
+ /// password byte sequence that is unrepresentable in UTF-8.
+ pub fn new(file: R, password: &[u8]) -> ZipCryptoReader<R> {
+ let mut result = ZipCryptoReader {
+ file,
+ keys: ZipCryptoKeys::new(),
+ };
+
+ // Key the cipher by updating the keys with the password.
+ for byte in password.iter() {
+ result.keys.update(*byte);
+ }
+
+ result
+ }
+
+ /// Read the ZipCrypto header bytes and validate the password.
+ pub fn validate(
+ mut self,
+ validator: ZipCryptoValidator,
+ ) -> Result<Option<ZipCryptoReaderValid<R>>, std::io::Error> {
+ // ZipCrypto prefixes a file with a 12 byte header
+ let mut header_buf = [0u8; 12];
+ self.file.read_exact(&mut header_buf)?;
+ for byte in header_buf.iter_mut() {
+ *byte = self.keys.decrypt_byte(*byte);
+ }
+
+ match validator {
+ ZipCryptoValidator::PkzipCrc32(crc32_plaintext) => {
+ // PKZIP before 2.0 used 2 byte CRC check.
+ // PKZIP 2.0+ used 1 byte CRC check. It's more secure.
+ // We also use 1 byte CRC.
+
+ if (crc32_plaintext >> 24) as u8 != header_buf[11] {
+ return Ok(None); // Wrong password
+ }
+ }
+ ZipCryptoValidator::InfoZipMsdosTime(last_mod_time) => {
+ // Info-ZIP modification to ZipCrypto format:
+ // If bit 3 of the general purpose bit flag is set
+ // (indicates that the file uses a data-descriptor section),
+ // it uses high byte of 16-bit File Time.
+ // Info-ZIP code probably writes 2 bytes of File Time.
+ // We check only 1 byte.
+
+ if (last_mod_time >> 8) as u8 != header_buf[11] {
+ return Ok(None); // Wrong password
+ }
+ }
+ }
+
+ Ok(Some(ZipCryptoReaderValid { reader: self }))
+ }
+}
+
+/// A ZipCrypto reader with verified password
+pub struct ZipCryptoReaderValid<R> {
+ reader: ZipCryptoReader<R>,
+}
+
+impl<R: std::io::Read> std::io::Read for ZipCryptoReaderValid<R> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ // Note: There might be potential for optimization. Inspiration can be found at:
+ // https://github.com/kornelski/7z/blob/master/CPP/7zip/Crypto/ZipCrypto.cpp
+
+ let result = self.reader.file.read(buf);
+ for byte in buf.iter_mut() {
+ *byte = self.reader.keys.decrypt_byte(*byte);
+ }
+ result
+ }
+}
+
+impl<R: std::io::Read> ZipCryptoReaderValid<R> {
+ /// Consumes this decoder, returning the underlying reader.
+ pub fn into_inner(self) -> R {
+ self.reader.file
+ }
+}
+
+static CRCTABLE: [u32; 256] = [
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+];
diff --git a/third_party/rust/zip/tests/aes_encryption.rs b/third_party/rust/zip/tests/aes_encryption.rs
new file mode 100644
index 0000000000..4b393ebf96
--- /dev/null
+++ b/third_party/rust/zip/tests/aes_encryption.rs
@@ -0,0 +1,80 @@
+#![cfg(feature = "aes-crypto")]
+
+use std::io::{self, Read};
+use zip::ZipArchive;
+
+const SECRET_CONTENT: &str = "Lorem ipsum dolor sit amet";
+
+const PASSWORD: &[u8] = b"helloworld";
+
+#[test]
+fn aes256_encrypted_uncompressed_file() {
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("data/aes_archive.zip"));
+ let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
+
+ let mut file = archive
+ .by_name_decrypt("secret_data_256_uncompressed", PASSWORD)
+ .expect("couldn't find file in archive")
+ .expect("invalid password");
+ assert_eq!("secret_data_256_uncompressed", file.name());
+
+ let mut content = String::new();
+ file.read_to_string(&mut content)
+ .expect("couldn't read encrypted file");
+ assert_eq!(SECRET_CONTENT, content);
+}
+
+#[test]
+fn aes256_encrypted_file() {
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("data/aes_archive.zip"));
+ let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
+
+ let mut file = archive
+ .by_name_decrypt("secret_data_256", PASSWORD)
+ .expect("couldn't find file in archive")
+ .expect("invalid password");
+ assert_eq!("secret_data_256", file.name());
+
+ let mut content = String::new();
+ file.read_to_string(&mut content)
+ .expect("couldn't read encrypted and compressed file");
+ assert_eq!(SECRET_CONTENT, content);
+}
+
+#[test]
+fn aes192_encrypted_file() {
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("data/aes_archive.zip"));
+ let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
+
+ let mut file = archive
+ .by_name_decrypt("secret_data_192", PASSWORD)
+ .expect("couldn't find file in archive")
+ .expect("invalid password");
+ assert_eq!("secret_data_192", file.name());
+
+ let mut content = String::new();
+ file.read_to_string(&mut content)
+ .expect("couldn't read encrypted file");
+ assert_eq!(SECRET_CONTENT, content);
+}
+
+#[test]
+fn aes128_encrypted_file() {
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("data/aes_archive.zip"));
+ let mut archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
+
+ let mut file = archive
+ .by_name_decrypt("secret_data_128", PASSWORD)
+ .expect("couldn't find file in archive")
+ .expect("invalid password");
+ assert_eq!("secret_data_128", file.name());
+
+ let mut content = String::new();
+ file.read_to_string(&mut content)
+ .expect("couldn't read encrypted file");
+ assert_eq!(SECRET_CONTENT, content);
+}
diff --git a/third_party/rust/zip/tests/data/aes_archive.zip b/third_party/rust/zip/tests/data/aes_archive.zip
new file mode 100644
index 0000000000..4cf1fd21ec
--- /dev/null
+++ b/third_party/rust/zip/tests/data/aes_archive.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/comment_garbage.zip b/third_party/rust/zip/tests/data/comment_garbage.zip
new file mode 100644
index 0000000000..f6a289e817
--- /dev/null
+++ b/third_party/rust/zip/tests/data/comment_garbage.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/files_and_dirs.zip b/third_party/rust/zip/tests/data/files_and_dirs.zip
new file mode 100644
index 0000000000..c444078a4a
--- /dev/null
+++ b/third_party/rust/zip/tests/data/files_and_dirs.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip b/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip
new file mode 100644
index 0000000000..a428ca7e36
--- /dev/null
+++ b/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip b/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip
new file mode 100644
index 0000000000..2cc90073f6
--- /dev/null
+++ b/third_party/rust/zip/tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/invalid_offset.zip b/third_party/rust/zip/tests/data/invalid_offset.zip
new file mode 100644
index 0000000000..9ff2e07fa8
--- /dev/null
+++ b/third_party/rust/zip/tests/data/invalid_offset.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/invalid_offset2.zip b/third_party/rust/zip/tests/data/invalid_offset2.zip
new file mode 100644
index 0000000000..944c611df1
--- /dev/null
+++ b/third_party/rust/zip/tests/data/invalid_offset2.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/mimetype.zip b/third_party/rust/zip/tests/data/mimetype.zip
new file mode 100644
index 0000000000..2d651cbeb3
--- /dev/null
+++ b/third_party/rust/zip/tests/data/mimetype.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/data/zip64_demo.zip b/third_party/rust/zip/tests/data/zip64_demo.zip
new file mode 100644
index 0000000000..f2ceee303e
--- /dev/null
+++ b/third_party/rust/zip/tests/data/zip64_demo.zip
Binary files differ
diff --git a/third_party/rust/zip/tests/end_to_end.rs b/third_party/rust/zip/tests/end_to_end.rs
new file mode 100644
index 0000000000..09e7ce47e8
--- /dev/null
+++ b/third_party/rust/zip/tests/end_to_end.rs
@@ -0,0 +1,205 @@
+use byteorder::{LittleEndian, WriteBytesExt};
+use std::collections::HashSet;
+use std::io::prelude::*;
+use std::io::{Cursor, Seek};
+use std::iter::FromIterator;
+use zip::write::FileOptions;
+use zip::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
+
+// This test asserts that after creating a zip file, then reading its contents back out,
+// the extracted data will *always* be exactly the same as the original data.
+#[test]
+fn end_to_end() {
+ for &method in SUPPORTED_COMPRESSION_METHODS {
+ let file = &mut Cursor::new(Vec::new());
+
+ println!("Writing file with {method} compression");
+ write_test_archive(file, method).expect("Couldn't write test zip archive");
+
+ println!("Checking file contents");
+ check_archive_file(file, ENTRY_NAME, Some(method), LOREM_IPSUM);
+ }
+}
+
+// This test asserts that after copying a `ZipFile` to a new `ZipWriter`, then reading its
+// contents back out, the extracted data will *always* be exactly the same as the original data.
+#[test]
+fn copy() {
+ for &method in SUPPORTED_COMPRESSION_METHODS {
+ let src_file = &mut Cursor::new(Vec::new());
+ write_test_archive(src_file, method).expect("Couldn't write to test file");
+
+ let mut tgt_file = &mut Cursor::new(Vec::new());
+
+ {
+ let mut src_archive = zip::ZipArchive::new(src_file).unwrap();
+ let mut zip = zip::ZipWriter::new(&mut tgt_file);
+
+ {
+ let file = src_archive
+ .by_name(ENTRY_NAME)
+ .expect("Missing expected file");
+
+ zip.raw_copy_file(file).expect("Couldn't copy file");
+ }
+
+ {
+ let file = src_archive
+ .by_name(ENTRY_NAME)
+ .expect("Missing expected file");
+
+ zip.raw_copy_file_rename(file, COPY_ENTRY_NAME)
+ .expect("Couldn't copy and rename file");
+ }
+ }
+
+ let mut tgt_archive = zip::ZipArchive::new(tgt_file).unwrap();
+
+ check_archive_file_contents(&mut tgt_archive, ENTRY_NAME, LOREM_IPSUM);
+ check_archive_file_contents(&mut tgt_archive, COPY_ENTRY_NAME, LOREM_IPSUM);
+ }
+}
+
+// This test asserts that after appending to a `ZipWriter`, then reading its contents back out,
+// both the prior data and the appended data will be exactly the same as their originals.
+#[test]
+fn append() {
+ for &method in SUPPORTED_COMPRESSION_METHODS {
+ let mut file = &mut Cursor::new(Vec::new());
+ write_test_archive(file, method).expect("Couldn't write to test file");
+
+ {
+ let mut zip = zip::ZipWriter::new_append(&mut file).unwrap();
+ zip.start_file(
+ COPY_ENTRY_NAME,
+ FileOptions::default().compression_method(method),
+ )
+ .unwrap();
+ zip.write_all(LOREM_IPSUM).unwrap();
+ zip.finish().unwrap();
+ }
+
+ let mut zip = zip::ZipArchive::new(&mut file).unwrap();
+ check_archive_file_contents(&mut zip, ENTRY_NAME, LOREM_IPSUM);
+ check_archive_file_contents(&mut zip, COPY_ENTRY_NAME, LOREM_IPSUM);
+ }
+}
+
+// Write a test zip archive to buffer.
+fn write_test_archive(
+ file: &mut Cursor<Vec<u8>>,
+ method: CompressionMethod,
+) -> zip::result::ZipResult<()> {
+ let mut zip = zip::ZipWriter::new(file);
+
+ zip.add_directory("test/", Default::default())?;
+
+ let options = FileOptions::default()
+ .compression_method(method)
+ .unix_permissions(0o755);
+
+ zip.start_file("test/☃.txt", options)?;
+ zip.write_all(b"Hello, World!\n")?;
+
+ zip.start_file_with_extra_data("test_with_extra_data/🐢.txt", options)?;
+ zip.write_u16::<LittleEndian>(0xbeef)?;
+ zip.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
+ zip.write_all(EXTRA_DATA)?;
+ zip.end_extra_data()?;
+ zip.write_all(b"Hello, World! Again.\n")?;
+
+ zip.start_file(ENTRY_NAME, options)?;
+ zip.write_all(LOREM_IPSUM)?;
+
+ zip.finish()?;
+ Ok(())
+}
+
+// Load an archive from buffer and check for test data.
+fn check_test_archive<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchive<R>> {
+ let mut archive = zip::ZipArchive::new(zip_file).unwrap();
+
+ // Check archive contains expected file names.
+ {
+ let expected_file_names = [
+ "test/",
+ "test/☃.txt",
+ "test_with_extra_data/🐢.txt",
+ ENTRY_NAME,
+ ];
+ let expected_file_names = HashSet::from_iter(expected_file_names.iter().copied());
+ let file_names = archive.file_names().collect::<HashSet<_>>();
+ assert_eq!(file_names, expected_file_names);
+ }
+
+ // Check an archive file for extra data field contents.
+ {
+ let file_with_extra_data = archive.by_name("test_with_extra_data/🐢.txt")?;
+ let mut extra_data = Vec::new();
+ extra_data.write_u16::<LittleEndian>(0xbeef)?;
+ extra_data.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
+ extra_data.write_all(EXTRA_DATA)?;
+ assert_eq!(file_with_extra_data.extra_data(), extra_data.as_slice());
+ }
+
+ Ok(archive)
+}
+
+// Read a file in the archive as a string.
+fn read_archive_file<R: Read + Seek>(
+ archive: &mut zip::ZipArchive<R>,
+ name: &str,
+) -> zip::result::ZipResult<String> {
+ let mut file = archive.by_name(name)?;
+
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).unwrap();
+
+ Ok(contents)
+}
+
+// Check a file in the archive contains expected data and properties.
+fn check_archive_file(
+ zip_file: &mut Cursor<Vec<u8>>,
+ name: &str,
+ expected_method: Option<CompressionMethod>,
+ expected_data: &[u8],
+) {
+ let mut archive = check_test_archive(zip_file).unwrap();
+
+ if let Some(expected_method) = expected_method {
+ // Check the file's compression method.
+ let file = archive.by_name(name).unwrap();
+ let real_method = file.compression();
+
+ assert_eq!(
+ expected_method, real_method,
+ "File does not have expected compression method"
+ );
+ }
+
+ check_archive_file_contents(&mut archive, name, expected_data);
+}
+
+// Check a file in the archive contains the given data.
+fn check_archive_file_contents<R: Read + Seek>(
+ archive: &mut zip::ZipArchive<R>,
+ name: &str,
+ expected: &[u8],
+) {
+ let file_contents: String = read_archive_file(archive, name).unwrap();
+ assert_eq!(file_contents.as_bytes(), expected);
+}
+
+const LOREM_IPSUM : &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet
+molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex,
+dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor
+vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
+inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
+";
+
+const EXTRA_DATA: &[u8] = b"Extra Data";
+
+const ENTRY_NAME: &str = "test/lorem_ipsum.txt";
+
+const COPY_ENTRY_NAME: &str = "test/lorem_ipsum_renamed.txt";
diff --git a/third_party/rust/zip/tests/invalid_date.rs b/third_party/rust/zip/tests/invalid_date.rs
new file mode 100644
index 0000000000..3f24e2514d
--- /dev/null
+++ b/third_party/rust/zip/tests/invalid_date.rs
@@ -0,0 +1,24 @@
+use std::io::Cursor;
+use zip::read::ZipArchive;
+
+const BUF: &[u8] = &[
+ 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x1c, 0x00, 0x69, 0x6e,
+ 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f,
+ 0x55, 0x54, 0x09, 0x00, 0x03, 0xf4, 0x5c, 0x88, 0x5a, 0xf4, 0x5c, 0x88, 0x5a, 0x75, 0x78, 0x0b,
+ 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02,
+ 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, // time part: 0 seconds, 0 minutes, 0 hours
+ 0x00, 0x00, // date part: day 0 (invalid), month 0 (invalid), year 0 (1980)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x18, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xed, 0x41, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6e,
+ 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f,
+ 0x55, 0x54, 0x05, 0x00, 0x03, 0xf4, 0x5c, 0x88, 0x5a, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8,
+ 0x03, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x58, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00,
+];
+
+#[test]
+fn invalid_date() {
+ let _archive = ZipArchive::new(Cursor::new(BUF)).unwrap();
+}
diff --git a/third_party/rust/zip/tests/issue_234.rs b/third_party/rust/zip/tests/issue_234.rs
new file mode 100644
index 0000000000..bd01d1d08a
--- /dev/null
+++ b/third_party/rust/zip/tests/issue_234.rs
@@ -0,0 +1,31 @@
+use zip::result::ZipError;
+
+const BUF: &[u8] = &[
+ 0, 80, 75, 1, 2, 127, 120, 0, 3, 3, 75, 80, 232, 3, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 7, 0, 0, 0,
+ 0, 65, 0, 1, 0, 0, 0, 4, 0, 0, 224, 255, 0, 255, 255, 255, 255, 255, 255, 20, 39, 221, 221,
+ 221, 221, 221, 221, 205, 221, 221, 221, 42, 221, 221, 221, 221, 221, 221, 221, 221, 38, 34, 34,
+ 219, 80, 75, 5, 6, 0, 0, 0, 0, 5, 96, 0, 1, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 234, 236, 124,
+ 221, 221, 37, 221, 221, 221, 221, 221, 129, 4, 0, 0, 221, 221, 80, 75, 1, 2, 127, 120, 0, 4, 0,
+ 0, 2, 127, 120, 0, 79, 75, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0,
+ 234, 0, 0, 0, 3, 8, 4, 232, 3, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 3, 0,
+ 221, 209, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 58, 58, 42, 75, 9, 2, 127,
+ 120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213, 213, 213, 213, 213, 213, 213,
+ 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213, 213,
+ 213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 234, 0, 0, 0, 3, 8,
+ 0, 0, 0, 12, 0, 0, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 0, 0, 58, 58, 58, 42, 175, 221, 253, 221,
+ 221, 221, 221, 221, 80, 75, 9, 2, 127, 120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0,
+ 213, 213, 213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236, 221, 221,
+ 221, 221, 221, 80, 75, 9, 2, 127, 120, 0, 99, 99, 99, 99, 99, 99, 94, 7, 0, 0, 0, 0, 0, 0, 213,
+ 213, 213, 213, 213, 213, 213, 213, 213, 7, 0, 0, 211, 211, 211, 211, 124, 236,
+];
+
+#[test]
+fn invalid_header() {
+ let reader = std::io::Cursor::new(&BUF);
+ let archive = zip::ZipArchive::new(reader);
+ match archive {
+ Err(ZipError::InvalidArchive(_)) => {}
+ value => panic!("Unexpected value: {:?}", value),
+ }
+}
diff --git a/third_party/rust/zip/tests/zip64_large.rs b/third_party/rust/zip/tests/zip64_large.rs
new file mode 100644
index 0000000000..468ef198f6
--- /dev/null
+++ b/third_party/rust/zip/tests/zip64_large.rs
@@ -0,0 +1,211 @@
+// The following is a hexdump of a zip64 file containing the following files:
+// zero4400: 4400 MB of zeroes
+// zero100: 100 MB of zeroes
+// zero4400_2: 4400 MB of zeroes
+//
+// 00000000 50 4b 03 04 2d 00 00 00 00 00 1b 6e 51 4d 66 82 |PK..-......nQMf.|
+// 00000010 13 da ff ff ff ff ff ff ff ff 08 00 30 00 7a 65 |............0.ze|
+// 00000020 72 6f 34 34 30 30 55 54 09 00 03 a5 21 c7 5b db |ro4400UT....!.[.|
+// 00000030 21 c7 5b 75 78 0b 00 01 04 e8 03 00 00 04 e8 03 |!.[ux...........|
+// 00000040 00 00 01 00 10 00 00 00 00 13 01 00 00 00 00 00 |................|
+// 00000050 00 13 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+// 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+// *
+// 113000050 00 00 00 00 00 00 50 4b 03 04 0a 00 00 00 00 00 |......PK........|
+// 113000060 2b 6e 51 4d 98 23 28 4b 00 00 40 06 00 00 40 06 |+nQM.#(K..@...@.|
+// 113000070 07 00 1c 00 7a 65 72 6f 31 30 30 55 54 09 00 03 |....zero100UT...|
+// 113000080 c2 21 c7 5b c2 21 c7 5b 75 78 0b 00 01 04 e8 03 |.!.[.!.[ux......|
+// 113000090 00 00 04 e8 03 00 00 00 00 00 00 00 00 00 00 00 |................|
+// 1130000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+// *
+// 119400090 00 00 00 00 00 00 00 50 4b 03 04 2d 00 00 00 00 |.......PK..-....|
+// 1194000a0 00 3b 6e 51 4d 66 82 13 da ff ff ff ff ff ff ff |.;nQMf..........|
+// 1194000b0 ff 0a 00 30 00 7a 65 72 6f 34 34 30 30 5f 32 55 |...0.zero4400_2U|
+// 1194000c0 54 09 00 03 e2 21 c7 5b db 21 c7 5b 75 78 0b 00 |T....!.[.!.[ux..|
+// 1194000d0 01 04 e8 03 00 00 04 e8 03 00 00 01 00 10 00 00 |................|
+// 1194000e0 00 00 13 01 00 00 00 00 00 00 13 01 00 00 00 00 |................|
+// 1194000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+// *
+// 22c4000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 |...............P|
+// 22c4000f0 4b 01 02 1e 03 2d 00 00 00 00 00 1b 6e 51 4d 66 |K....-......nQMf|
+// 22c400100 82 13 da ff ff ff ff ff ff ff ff 08 00 2c 00 00 |.............,..|
+// 22c400110 00 00 00 00 00 00 00 a4 81 00 00 00 00 7a 65 72 |.............zer|
+// 22c400120 6f 34 34 30 30 55 54 05 00 03 a5 21 c7 5b 75 78 |o4400UT....!.[ux|
+// 22c400130 0b 00 01 04 e8 03 00 00 04 e8 03 00 00 01 00 10 |................|
+// 22c400140 00 00 00 00 13 01 00 00 00 00 00 00 13 01 00 00 |................|
+// 22c400150 00 50 4b 01 02 1e 03 0a 00 00 00 00 00 2b 6e 51 |.PK..........+nQ|
+// 22c400160 4d 98 23 28 4b 00 00 40 06 00 00 40 06 07 00 24 |M.#(K..@...@...$|
+// 22c400170 00 00 00 00 00 00 00 00 00 a4 81 ff ff ff ff 7a |...............z|
+// 22c400180 65 72 6f 31 30 30 55 54 05 00 03 c2 21 c7 5b 75 |ero100UT....!.[u|
+// 22c400190 78 0b 00 01 04 e8 03 00 00 04 e8 03 00 00 01 00 |x...............|
+// 22c4001a0 08 00 56 00 00 13 01 00 00 00 50 4b 01 02 1e 03 |..V.......PK....|
+// 22c4001b0 2d 00 00 00 00 00 3b 6e 51 4d 66 82 13 da ff ff |-.....;nQMf.....|
+// 22c4001c0 ff ff ff ff ff ff 0a 00 34 00 00 00 00 00 00 00 |........4.......|
+// 22c4001d0 00 00 a4 81 ff ff ff ff 7a 65 72 6f 34 34 30 30 |........zero4400|
+// 22c4001e0 5f 32 55 54 05 00 03 e2 21 c7 5b 75 78 0b 00 01 |_2UT....!.[ux...|
+// 22c4001f0 04 e8 03 00 00 04 e8 03 00 00 01 00 18 00 00 00 |................|
+// 22c400200 00 13 01 00 00 00 00 00 00 13 01 00 00 00 97 00 |................|
+// 22c400210 40 19 01 00 00 00 50 4b 06 06 2c 00 00 00 00 00 |@.....PK..,.....|
+// 22c400220 00 00 1e 03 2d 00 00 00 00 00 00 00 00 00 03 00 |....-...........|
+// 22c400230 00 00 00 00 00 00 03 00 00 00 00 00 00 00 27 01 |..............'.|
+// 22c400240 00 00 00 00 00 00 ef 00 40 2c 02 00 00 00 50 4b |........@,....PK|
+// 22c400250 06 07 00 00 00 00 16 02 40 2c 02 00 00 00 01 00 |........@,......|
+// 22c400260 00 00 50 4b 05 06 00 00 00 00 03 00 03 00 27 01 |..PK..........'.|
+// 22c400270 00 00 ff ff ff ff 00 00 |........|
+// 22c400278
+use std::io::{self, Read, Seek, SeekFrom};
+
+const BLOCK1_LENGTH: u64 = 0x60;
+const BLOCK1: [u8; BLOCK1_LENGTH as usize] = [
+ 0x50, 0x4b, 0x03, 0x04, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x6e, 0x51, 0x4d, 0x66, 0x82,
+ 0x13, 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x30, 0x00, 0x7a, 0x65,
+ 0x72, 0x6f, 0x34, 0x34, 0x30, 0x30, 0x55, 0x54, 0x09, 0x00, 0x03, 0xa5, 0x21, 0xc7, 0x5b, 0xdb,
+ 0x21, 0xc7, 0x5b, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03,
+ 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+];
+
+const BLOCK2_LENGTH: u64 = 0x50;
+const BLOCK2: [u8; BLOCK2_LENGTH as usize] = [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2b, 0x6e, 0x51, 0x4d, 0x98, 0x23, 0x28, 0x4b, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x40, 0x06,
+ 0x07, 0x00, 0x1c, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x31, 0x30, 0x30, 0x55, 0x54, 0x09, 0x00, 0x03,
+ 0xc2, 0x21, 0xc7, 0x5b, 0xc2, 0x21, 0xc7, 0x5b, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03,
+ 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+];
+
+const BLOCK3_LENGTH: u64 = 0x60;
+const BLOCK3: [u8; BLOCK3_LENGTH as usize] = [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x03, 0x04, 0x2d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x3b, 0x6e, 0x51, 0x4d, 0x66, 0x82, 0x13, 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x0a, 0x00, 0x30, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x34, 0x34, 0x30, 0x30, 0x5f, 0x32, 0x55,
+ 0x54, 0x09, 0x00, 0x03, 0xe2, 0x21, 0xc7, 0x5b, 0xdb, 0x21, 0xc7, 0x5b, 0x75, 0x78, 0x0b, 0x00,
+ 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00,
+];
+
+const BLOCK4_LENGTH: u64 = 0x198;
+const BLOCK4: [u8; BLOCK4_LENGTH as usize] = [
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
+ 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x6e, 0x51, 0x4d, 0x66,
+ 0x82, 0x13, 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x2c, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72,
+ 0x6f, 0x34, 0x34, 0x30, 0x30, 0x55, 0x54, 0x05, 0x00, 0x03, 0xa5, 0x21, 0xc7, 0x5b, 0x75, 0x78,
+ 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00,
+ 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x6e, 0x51,
+ 0x4d, 0x98, 0x23, 0x28, 0x4b, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x40, 0x06, 0x07, 0x00, 0x24,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0xff, 0xff, 0xff, 0xff, 0x7a,
+ 0x65, 0x72, 0x6f, 0x31, 0x30, 0x30, 0x55, 0x54, 0x05, 0x00, 0x03, 0xc2, 0x21, 0xc7, 0x5b, 0x75,
+ 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00,
+ 0x08, 0x00, 0x56, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03,
+ 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x6e, 0x51, 0x4d, 0x66, 0x82, 0x13, 0xda, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xa4, 0x81, 0xff, 0xff, 0xff, 0xff, 0x7a, 0x65, 0x72, 0x6f, 0x34, 0x34, 0x30, 0x30,
+ 0x5f, 0x32, 0x55, 0x54, 0x05, 0x00, 0x03, 0xe2, 0x21, 0xc7, 0x5b, 0x75, 0x78, 0x0b, 0x00, 0x01,
+ 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0x00, 0x00, 0x00, 0x97, 0x00,
+ 0x40, 0x19, 0x01, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x06, 0x06, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x1e, 0x03, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0x00, 0x40, 0x2c, 0x02, 0x00, 0x00, 0x00, 0x50, 0x4b,
+ 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x16, 0x02, 0x40, 0x2c, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x27, 0x01,
+ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
+];
+
+const BLOCK1_START: u64 = 0x000000000;
+const BLOCK2_START: u64 = 0x113000050;
+const BLOCK3_START: u64 = 0x119400090;
+const BLOCK4_START: u64 = 0x22c4000e0;
+
+const BLOCK1_END: u64 = BLOCK1_START + BLOCK1_LENGTH - 1;
+const BLOCK2_END: u64 = BLOCK2_START + BLOCK2_LENGTH - 1;
+const BLOCK3_END: u64 = BLOCK3_START + BLOCK3_LENGTH - 1;
+const BLOCK4_END: u64 = BLOCK4_START + BLOCK4_LENGTH - 1;
+
+const TOTAL_LENGTH: u64 = BLOCK4_START + BLOCK4_LENGTH;
+
+struct Zip64File {
+ pointer: u64,
+}
+
+impl Zip64File {
+ fn new() -> Self {
+ Zip64File { pointer: 0 }
+ }
+}
+
+impl Seek for Zip64File {
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+ match pos {
+ SeekFrom::Start(offset) => {
+ self.pointer = offset;
+ }
+ SeekFrom::End(offset) => {
+ if offset > 0 || offset < -(TOTAL_LENGTH as i64) {
+ return Err(io::Error::new(io::ErrorKind::Other, "Invalid seek offset"));
+ }
+ self.pointer = (TOTAL_LENGTH as i64 + offset) as u64;
+ }
+ SeekFrom::Current(offset) => {
+ let seekpos = self.pointer as i64 + offset;
+ if seekpos < 0 || seekpos as u64 > TOTAL_LENGTH {
+ return Err(io::Error::new(io::ErrorKind::Other, "Invalid seek offset"));
+ }
+ self.pointer = seekpos as u64;
+ }
+ }
+ Ok(self.pointer)
+ }
+}
+
+impl Read for Zip64File {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ if self.pointer >= TOTAL_LENGTH {
+ return Ok(0);
+ }
+ match self.pointer {
+ BLOCK1_START..=BLOCK1_END => {
+ buf[0] = BLOCK1[(self.pointer - BLOCK1_START) as usize];
+ }
+ BLOCK2_START..=BLOCK2_END => {
+ buf[0] = BLOCK2[(self.pointer - BLOCK2_START) as usize];
+ }
+ BLOCK3_START..=BLOCK3_END => {
+ buf[0] = BLOCK3[(self.pointer - BLOCK3_START) as usize];
+ }
+ BLOCK4_START..=BLOCK4_END => {
+ buf[0] = BLOCK4[(self.pointer - BLOCK4_START) as usize];
+ }
+ _ => {
+ buf[0] = 0;
+ }
+ }
+ self.pointer += 1;
+ Ok(1)
+ }
+}
+
+#[test]
+fn zip64_large() {
+ let zipfile = Zip64File::new();
+ let mut archive = zip::ZipArchive::new(zipfile).unwrap();
+ let mut buf = [0u8; 32];
+
+ for i in 0..archive.len() {
+ let mut file = archive.by_index(i).unwrap();
+ let outpath = file.enclosed_name().unwrap();
+ println!(
+ "Entry {} has name \"{}\" ({} bytes)",
+ i,
+ outpath.display(),
+ file.size()
+ );
+
+ match file.read_exact(&mut buf) {
+ Ok(()) => println!("The first {} bytes are: {:?}", buf.len(), buf),
+ Err(e) => println!("Could not read the file: {e:?}"),
+ };
+ }
+}
diff --git a/third_party/rust/zip/tests/zip_comment_garbage.rs b/third_party/rust/zip/tests/zip_comment_garbage.rs
new file mode 100644
index 0000000000..ef4d975079
--- /dev/null
+++ b/third_party/rust/zip/tests/zip_comment_garbage.rs
@@ -0,0 +1,30 @@
+// Some zip files can contain garbage after the comment. For example, python zipfile generates
+// it when opening a zip in 'a' mode:
+//
+// >>> from zipfile import ZipFile
+// >>> with ZipFile('comment_garbage.zip', 'a') as z:
+// ... z.comment = b'long comment bla bla bla'
+// ...
+// >>> with ZipFile('comment_garbage.zip', 'a') as z:
+// ... z.comment = b'short.'
+// ...
+// >>>
+//
+// Hexdump:
+//
+// 00000000 50 4b 05 06 00 00 00 00 00 00 00 00 00 00 00 00 |PK..............|
+// 00000010 00 00 00 00 06 00 73 68 6f 72 74 2e 6f 6d 6d 65 |......short.omme|
+// 00000020 6e 74 20 62 6c 61 20 62 6c 61 20 62 6c 61 |nt bla bla bla|
+// 0000002e
+
+use std::io;
+use zip::ZipArchive;
+
+#[test]
+fn correctly_handle_zip_with_garbage_after_comment() {
+ let mut v = Vec::new();
+ v.extend_from_slice(include_bytes!("../tests/data/comment_garbage.zip"));
+ let archive = ZipArchive::new(io::Cursor::new(v)).expect("couldn't open test zip file");
+
+ assert_eq!(archive.comment(), "short.".as_bytes());
+}
diff --git a/third_party/rust/zip/tests/zip_crypto.rs b/third_party/rust/zip/tests/zip_crypto.rs
new file mode 100644
index 0000000000..6c4d6b811c
--- /dev/null
+++ b/third_party/rust/zip/tests/zip_crypto.rs
@@ -0,0 +1,85 @@
+// The following is a hexdump of a zip file containing the following
+// ZipCrypto encrypted file:
+// test.txt: 35 bytes, contents: `abcdefghijklmnopqrstuvwxyz123456789`, password: `test`
+//
+// 00000000 50 4b 03 04 14 00 01 00 00 00 54 bd b5 50 2f 20 |PK........T..P/ |
+// 00000010 79 55 2f 00 00 00 23 00 00 00 08 00 00 00 74 65 |yU/...#.......te|
+// 00000020 73 74 2e 74 78 74 ca 2d 1d 27 19 19 63 43 77 9a |st.txt.-.'..cCw.|
+// 00000030 71 76 c9 ec d1 6f d9 f5 22 67 b3 8f 52 b5 41 bc |qv...o.."g..R.A.|
+// 00000040 5c 36 f2 1d 84 c3 c0 28 3b fd e1 70 c2 cc 0c 11 |\6.....(;..p....|
+// 00000050 0c c5 95 2f a4 50 4b 01 02 3f 00 14 00 01 00 00 |.../.PK..?......|
+// 00000060 00 54 bd b5 50 2f 20 79 55 2f 00 00 00 23 00 00 |.T..P/ yU/...#..|
+// 00000070 00 08 00 24 00 00 00 00 00 00 00 20 00 00 00 00 |...$....... ....|
+// 00000080 00 00 00 74 65 73 74 2e 74 78 74 0a 00 20 00 00 |...test.txt.. ..|
+// 00000090 00 00 00 01 00 18 00 31 b2 3b bf b8 2f d6 01 31 |.......1.;../..1|
+// 000000a0 b2 3b bf b8 2f d6 01 a8 c4 45 bd b8 2f d6 01 50 |.;../....E../..P|
+// 000000b0 4b 05 06 00 00 00 00 01 00 01 00 5a 00 00 00 55 |K..........Z...U|
+// 000000c0 00 00 00 00 00 |.....|
+// 000000c5
+
+use std::io::Cursor;
+use std::io::Read;
+
+#[test]
+fn encrypted_file() {
+ let zip_file_bytes = &mut Cursor::new(vec![
+ 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0xbd, 0xb5, 0x50, 0x2f,
+ 0x20, 0x79, 0x55, 0x2f, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, 0xca, 0x2d, 0x1d, 0x27, 0x19, 0x19, 0x63,
+ 0x43, 0x77, 0x9a, 0x71, 0x76, 0xc9, 0xec, 0xd1, 0x6f, 0xd9, 0xf5, 0x22, 0x67, 0xb3, 0x8f,
+ 0x52, 0xb5, 0x41, 0xbc, 0x5c, 0x36, 0xf2, 0x1d, 0x84, 0xc3, 0xc0, 0x28, 0x3b, 0xfd, 0xe1,
+ 0x70, 0xc2, 0xcc, 0x0c, 0x11, 0x0c, 0xc5, 0x95, 0x2f, 0xa4, 0x50, 0x4b, 0x01, 0x02, 0x3f,
+ 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0xbd, 0xb5, 0x50, 0x2f, 0x20, 0x79, 0x55,
+ 0x2f, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74,
+ 0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18,
+ 0x00, 0x31, 0xb2, 0x3b, 0xbf, 0xb8, 0x2f, 0xd6, 0x01, 0x31, 0xb2, 0x3b, 0xbf, 0xb8, 0x2f,
+ 0xd6, 0x01, 0xa8, 0xc4, 0x45, 0xbd, 0xb8, 0x2f, 0xd6, 0x01, 0x50, 0x4b, 0x05, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ ]);
+
+ let mut archive = zip::ZipArchive::new(zip_file_bytes).unwrap();
+
+ assert_eq!(archive.len(), 1); //Only one file inside archive: `test.txt`
+
+ {
+ // No password
+ let file = archive.by_index(0);
+ match file {
+ Err(zip::result::ZipError::UnsupportedArchive(
+ zip::result::ZipError::PASSWORD_REQUIRED,
+ )) => (),
+ Err(_) => panic!(
+ "Expected PasswordRequired error when opening encrypted file without password"
+ ),
+ Ok(_) => panic!("Error: Successfully opened encrypted file without password?!"),
+ }
+ }
+
+ {
+ // Wrong password
+ let file = archive.by_index_decrypt(0, b"wrong password");
+ match file {
+ Ok(Err(zip::result::InvalidPassword)) => (),
+ Err(_) => panic!(
+ "Expected InvalidPassword error when opening encrypted file with wrong password"
+ ),
+ Ok(Ok(_)) => panic!("Error: Successfully opened encrypted file with wrong password?!"),
+ }
+ }
+
+ {
+ // Correct password, read contents
+ let mut file = archive
+ .by_index_decrypt(0, "test".as_bytes())
+ .unwrap()
+ .unwrap();
+ let file_name = file.enclosed_name().unwrap();
+ assert_eq!(file_name, std::path::PathBuf::from("test.txt"));
+
+ let mut data = Vec::new();
+ file.read_to_end(&mut data).unwrap();
+ assert_eq!(data, "abcdefghijklmnopqrstuvwxyz123456789".as_bytes());
+ }
+}