astral_async_zip-0.0.17/.cargo_vcs_info.json0000644000000001360000000000100144660ustar { "git": { "sha1": "72de959383b1788aa2c0457e29526a1dacf58faa" }, "path_in_vcs": "" }astral_async_zip-0.0.17/.github/dependabot.yml000064400000000000000000000004361046102023000174510ustar 00000000000000version: 2 updates: - package-ecosystem: "github-actions" # Workflow files stored in the # default location of `.github/workflows` directory: "/" schedule: interval: "daily" - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" astral_async_zip-0.0.17/.github/workflows/ci-clippy.yml000064400000000000000000000004501046102023000212660ustar 00000000000000name: clippy (Linux) on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run clippy run: cargo clippy --all-features -- -D clippy::allastral_async_zip-0.0.17/.github/workflows/ci-fmt.yml000064400000000000000000000004161046102023000205560ustar 00000000000000name: rustfmt (Linux) on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run rustfmt run: cargo fmt --checkastral_async_zip-0.0.17/.github/workflows/ci-linux.yml000064400000000000000000000021611046102023000211260ustar 00000000000000name: Test (Linux) on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Test [no features] run: cargo test --verbose - name: Test ['chrono' feature] run: cargo test --verbose --features chrono - name: Test ['tokio' feature] run: cargo test --verbose --features tokio - name: Test ['tokio-fs' feature] run: cargo test --verbose --features tokio-fs - name: Test ['deflate' feature] run: cargo test --verbose --features deflate - name: Test ['bzip2' feature] run: cargo test --verbose --features bzip2 - name: Test ['lzma' feature] run: cargo test --verbose --features lzma - name: Test ['zstd' feature] run: cargo test --verbose --features zstd - name: Test ['xz' feature] run: cargo test --verbose --features xz - name: Test ['deflate64' feature] run: cargo test --verbose --features deflate64 - name: Test ['full' feature] run: cargo test --verbose --features full astral_async_zip-0.0.17/.github/workflows/ci-typos.yml000064400000000000000000000005141046102023000211450ustar 00000000000000name: typos (Linux) on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install typos run: cargo install typos-cli - name: Run typos run: typos --format briefastral_async_zip-0.0.17/.github/workflows/ci-wasm.yml000064400000000000000000000010001046102023000207250ustar 00000000000000name: Build (WASM) on: push: branches: [ main ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always jobs: build: name: Build ['full-wasm' feature] on ${{ matrix.target }} runs-on: ubuntu-latest strategy: matrix: target: - wasm32-wasi - wasm32-unknown-unknown steps: - uses: actions/checkout@v4 - run: rustup target add ${{ matrix.target }} - run: cargo build --verbose --target ${{ matrix.target }} --features full-wasm astral_async_zip-0.0.17/.gitignore000064400000000000000000000006771046102023000152600ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ /examples/**/target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html /Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk /examples/**/*.rs.bk # Ignore generated zip test file that is large /src/tests/read/zip64/zip64many.zip astral_async_zip-0.0.17/Cargo.lock0000644000001020250000000000100124410ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "astral_async_zip" version = "0.0.17" dependencies = [ "async-compression", "crc32fast", "env_logger", "futures-lite", "pin-project", "thiserror", "tokio", "tokio-util", "zip", ] [[package]] name = "async-compression" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "bzip2 0.4.4", "deflate64", "flate2", "futures-core", "futures-io", "memchr", "pin-project-lite", "xz2", "zstd", "zstd-safe", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide 0.7.1", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[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" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", ] [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", "pkg-config", ] [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", ] [[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.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", ] [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[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 = "deflate64" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", "subtle", ] [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", ] [[package]] name = "futures-core" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-io" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-sink" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "generic-array", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "jobserver" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "lockfree-object-pool" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lzma-rs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" dependencies = [ "byteorder", "crc", ] [[package]] name = "lzma-sys" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.48.5", ] [[package]] name = "pbkdf2" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", ] [[package]] name = "pin-project" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", ] [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "num-conv", "powerfmt", "serde", "time-core", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "tokio" version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-util" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.4.1", ] [[package]] name = "xz2" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" dependencies = [ "lzma-sys", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zip" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" dependencies = [ "aes", "arbitrary", "bzip2 0.5.2", "constant_time_eq", "crc32fast", "crossbeam-utils", "deflate64", "flate2", "getrandom", "hmac", "indexmap", "lzma-rs", "memchr", "pbkdf2", "sha1", "time", "xz2", "zeroize", "zopfli", "zstd", ] [[package]] name = "zopfli" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" dependencies = [ "bumpalo", "crc32fast", "lockfree-object-pool", "log", "once_cell", "simd-adler32", ] [[package]] name = "zstd" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", ] astral_async_zip-0.0.17/Cargo.toml0000644000000046270000000000100124750ustar # 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 = "2021" name = "astral_async_zip" version = "0.0.17" authors = ["Harry [hello@majored.pw]"] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "An asynchronous ZIP archive reading/writing crate." homepage = "https://github.com/Majored/rs-async-zip" documentation = "https://docs.rs/async_zip/" readme = "README.md" keywords = [ "async", "zip", "archive", "tokio", ] categories = [ "asynchronous", "compression", ] license = "MIT" repository = "https://github.com/Majored/rs-async-zip" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] bzip2 = ["async-compression/bzip2"] deflate = ["async-compression/deflate"] deflate64 = ["async-compression/deflate64"] full = [ "tokio-fs", "deflate", "bzip2", "lzma", "zstd", "xz", "deflate64", ] full-wasm = [ "deflate", "zstd", ] lzma = ["async-compression/lzma"] tokio = [ "dep:tokio", "tokio-util", "tokio/io-util", ] tokio-fs = ["tokio/fs"] xz = ["async-compression/xz"] zstd = ["async-compression/zstd"] [lib] name = "async_zip" path = "src/lib.rs" [[test]] name = "decompress_test" path = "tests/decompress_test.rs" [dependencies.async-compression] version = "0.4.2" features = ["futures-io"] optional = true default-features = false [dependencies.crc32fast] version = "1" [dependencies.futures-lite] version = "2.1.0" features = ["std"] default-features = false [dependencies.pin-project] version = "1" [dependencies.thiserror] version = "1" [dependencies.tokio] version = "1" optional = true default-features = false [dependencies.tokio-util] version = "0.7" features = ["compat"] optional = true [dev-dependencies.env_logger] version = "0.11.2" [dev-dependencies.tokio] version = "1" features = ["full"] [dev-dependencies.tokio-util] version = "0.7" features = ["compat"] [dev-dependencies.zip] version = "2.1.5" astral_async_zip-0.0.17/Cargo.toml.orig000064400000000000000000000031621046102023000161470ustar 00000000000000[package] name = "astral_async_zip" version = "0.0.17" edition = "2021" authors = ["Harry [hello@majored.pw]"] repository = "https://github.com/Majored/rs-async-zip" description = "An asynchronous ZIP archive reading/writing crate." readme = "README.md" license = "MIT" documentation = "https://docs.rs/async_zip/" homepage = "https://github.com/Majored/rs-async-zip" keywords = ["async", "zip", "archive", "tokio"] categories = ["asynchronous", "compression"] [lib] name = "async_zip" [features] full = ["tokio-fs", "deflate", "bzip2", "lzma", "zstd", "xz", "deflate64"] # All features that are compatible with WASM full-wasm = ["deflate", "zstd"] tokio = ["dep:tokio", "tokio-util", "tokio/io-util"] tokio-fs = ["tokio/fs"] deflate = ["async-compression/deflate"] bzip2 = ["async-compression/bzip2"] lzma = ["async-compression/lzma"] zstd = ["async-compression/zstd"] xz = ["async-compression/xz"] deflate64 = ["async-compression/deflate64"] [package.metadata.docs.rs] all-features = true # defines the configuration attribute `docsrs` rustdoc-args = ["--cfg", "docsrs"] [dependencies] crc32fast = "1" futures-lite = { version = "2.1.0", default-features = false, features = ["std"] } pin-project = "1" thiserror = "1" async-compression = { version = "0.4.2", default-features = false, features = ["futures-io"], optional = true } tokio = { version = "1", default-features = false, optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } [dev-dependencies] # tests tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7", features = ["compat"] } env_logger = "0.11.2" zip = "2.1.5" astral_async_zip-0.0.17/LICENSE000064400000000000000000000021041046102023000142600ustar 00000000000000MIT License Copyright (c) 2021 Harry Copyright (c) 2023 Cognite AS 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. astral_async_zip-0.0.17/README.md000064400000000000000000000011221046102023000145310ustar 00000000000000# async_zip A fork of [`rs-async-zip`](https://github.com/Majored/rs-async-zip) intended for use in [uv](https://github.com/astral-sh/uv). As compared to [`rs-async-zip`](https://github.com/Majored/rs-async-zip), this fork contains the following modifications: - Support for streaming the central directory and end of central directory records. - Support for tracking offsets during streamed reads. - Support for accessing data descriptors during streamed reads. - Stricter validation around extra field headers. - Minor changes to better align with the Python ecosystem's `zipfile` module. astral_async_zip-0.0.17/rustfmt.toml000064400000000000000000000000541046102023000156560ustar 00000000000000max_width = 120 use_small_heuristics = "Max"astral_async_zip-0.0.17/src/base/mod.rs000064400000000000000000000003331046102023000161030ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A base runtime-agnostic implementation using `futures`'s IO types. pub mod read; astral_async_zip-0.0.17/src/base/read/cd.rs000064400000000000000000000250211046102023000166260ustar 00000000000000use futures_lite::io::{AsyncRead, AsyncReadExt}; use crate::base::read::counting::Counting; use crate::base::read::io::CombinedCentralDirectoryRecord; use crate::base::read::{detect_filename, get_zip64_extra_field, io}; use crate::error::{Result, ZipError}; use crate::spec::consts::{CDH_SIGNATURE, EOCDR_SIGNATURE, NON_ZIP64_MAX_SIZE, ZIP64_EOCDR_SIGNATURE}; use crate::spec::header::{ CentralDirectoryRecord, EndOfCentralDirectoryHeader, Zip64EndOfCentralDirectoryLocator, Zip64EndOfCentralDirectoryRecord, }; use crate::spec::parse::parse_extra_fields; use crate::ZipString; /// An entry returned by the [`CentralDirectoryReader`]. pub enum Entry { CentralDirectoryEntry(CentralDirectoryEntry), EndOfCentralDirectoryRecord { /// The combined end-of-central-directory record, which may include ZIP64 information. record: CombinedCentralDirectoryRecord, /// The comment associated with the end-of-central-directory record. comment: ZipString, /// Whether the end-of-central-directory record contains extensible data. extensible: bool, }, } /// An entry in the ZIP file's central directory. pub struct CentralDirectoryEntry { /// The compressed size of the entry, taking into account ZIP64 if necessary. pub(crate) compressed_size: u64, /// The uncompressed size of the entry, taking into account ZIP64 if necessary. pub(crate) uncompressed_size: u64, /// The file offset of the entry in the ZIP file, taking into account ZIP64 if necessary. pub(crate) lh_offset: u64, /// The end-of-central-directory record header. pub(crate) header: CentralDirectoryRecord, /// The filename of the entry. pub(crate) filename: ZipString, } impl CentralDirectoryEntry { /// Returns the entry's filename. /// /// ## Note /// This will return the raw filename stored during ZIP creation. If calling this method on entries retrieved from /// untrusted ZIP files, the filename should be sanitised before being used as a path to prevent [directory /// traversal attacks](https://en.wikipedia.org/wiki/Directory_traversal_attack). pub fn filename(&self) -> &ZipString { &self.filename } /// Returns whether or not the entry represents a directory. pub fn dir(&self) -> Result { Ok(self.filename.as_str()?.ends_with('/')) } /// Returns the entry's integer-based UNIX permissions. pub fn unix_permissions(&self) -> Option { Some((self.header.exter_attr) >> 16) } /// Returns the CRC32 checksum of the entry. pub fn crc32(&self) -> u32 { self.header.crc } /// Returns the file offset of the entry in the ZIP file. pub fn file_offset(&self) -> u64 { self.lh_offset } /// Returns the entry's compressed size. pub fn compressed_size(&self) -> u64 { self.compressed_size } /// Returns the entry's uncompressed size. pub fn uncompressed_size(&self) -> u64 { self.uncompressed_size } } #[derive(Clone)] pub struct CentralDirectoryReader { reader: R, initial: bool, offset: u64, } impl CentralDirectoryReader> where R: AsyncRead + Unpin, { /// Constructs a new ZIP reader from a non-seekable source. pub fn new(reader: R, offset: u64) -> Self { Self { reader: Counting::new(reader), offset, initial: true } } /// Reads the next [`CentralDirectoryEntry`] from the underlying source, advancing the /// reader to the next record. /// /// Returns `Ok(EndOfCentralDirectoryRecord)` if the end of the central directory record has /// been reached. pub async fn next(&mut self) -> Result { // Skip the first `CDH_SIGNATURE`. The `CentralDirectoryReader` is assumed to pick up from // where the streaming `ZipFileReader` left off, which means that the first record's // signature has already been read. if self.initial { self.initial = false; } else { let signature = { let mut buffer = [0; 4]; self.reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; let offset = self.offset + self.reader.bytes_read(); match signature { CDH_SIGNATURE => (), EOCDR_SIGNATURE => { // Read the end-of-central-directory header. let eocdr = EndOfCentralDirectoryHeader::from_reader(&mut self.reader).await?; // Read the EOCDR comment. let comment = io::read_string(&mut self.reader, eocdr.file_comm_length.into(), crate::StringEncoding::Utf8) .await?; // Verify that the EOCDR offset matches the current reader offset. if eocdr.central_directory_offset() != self.offset { return Err(ZipError::InvalidEndOfCentralDirectoryOffset( eocdr.central_directory_offset(), offset, )); } return Ok(Entry::EndOfCentralDirectoryRecord { record: CombinedCentralDirectoryRecord::from(&eocdr), comment, extensible: false, }); } ZIP64_EOCDR_SIGNATURE => { // Read the ZIP64 EOCDR. let zip64_eocdr = Zip64EndOfCentralDirectoryRecord::from_reader(&mut self.reader).await?; // Skip the extensible data field. let extensible = if zip64_eocdr.size_of_zip64_end_of_cd_record > 44 { let extensible_data_size = zip64_eocdr.size_of_zip64_end_of_cd_record - 44; io::skip_bytes(&mut self.reader, extensible_data_size).await?; true } else { false }; // Read the ZIP64 EOCDR locator. let Some(zip64_eocdl) = Zip64EndOfCentralDirectoryLocator::try_from_reader(&mut self.reader).await? else { return Err(ZipError::MissingZip64EndOfCentralDirectoryLocator); }; // Verify that the ZIP64 EOCDR locator points to the correct offset. if zip64_eocdl.relative_offset != offset { return Err(ZipError::InvalidZip64EndOfCentralDirectoryLocatorOffset( zip64_eocdl.relative_offset, offset, )); } // Read the EOCDR signature. let signature = { let mut buffer = [0; 4]; self.reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; if signature != EOCDR_SIGNATURE { return Err(ZipError::UnexpectedHeaderError(signature, EOCDR_SIGNATURE)); } // Read the end-of-central-directory header. let eocdr = EndOfCentralDirectoryHeader::from_reader(&mut self.reader).await?; // Read the EOCDR comment. let comment = io::read_string(&mut self.reader, eocdr.file_comm_length.into(), crate::StringEncoding::Utf8) .await?; // Combine the EOCDR and ZIP64 EOCDR. let combined = CombinedCentralDirectoryRecord::combine(eocdr, zip64_eocdr); // Verify that the EOCDR offset matches the current reader offset. if combined.central_directory_offset() != self.offset { return Err(ZipError::InvalidEndOfCentralDirectoryOffset( combined.central_directory_offset(), offset, )); } return Ok(Entry::EndOfCentralDirectoryRecord { record: combined, comment, extensible }); } actual => return Err(ZipError::UnexpectedHeaderError(actual, CDH_SIGNATURE)), } } // Read the record. let header = CentralDirectoryRecord::from_reader(&mut self.reader).await?; // Read the file name, extra field, and comment, which also ensures that we advance the // reader to the next record. let filename_basic = io::read_bytes(&mut self.reader, header.file_name_length.into()).await?; let extra_field = io::read_bytes(&mut self.reader, header.extra_field_length.into()).await?; let extra_fields = parse_extra_fields( extra_field, header.uncompressed_size, header.compressed_size, Some(header.lh_offset), Some(header.disk_start), )?; let zip64_extra_field = get_zip64_extra_field(&extra_fields); // We read the comment but drop it, since we don't need it for anything. io::skip_bytes(&mut self.reader, header.file_comment_length.into()).await?; // Reconcile the compressed size, uncompressed size, and file offset, using ZIP64 if necessary. let compressed_size = if let Some(compressed_size) = zip64_extra_field .and_then(|zip64| zip64.compressed_size) .filter(|_| header.compressed_size == NON_ZIP64_MAX_SIZE) { compressed_size } else { header.compressed_size as u64 }; let uncompressed_size = if let Some(uncompressed_size) = zip64_extra_field .and_then(|zip64| zip64.uncompressed_size) .filter(|_| header.uncompressed_size == NON_ZIP64_MAX_SIZE) { uncompressed_size } else { header.uncompressed_size as u64 }; let lh_offset = if let Some(lh_offset) = zip64_extra_field .and_then(|zip64| zip64.relative_header_offset) .filter(|_| header.lh_offset == NON_ZIP64_MAX_SIZE) { lh_offset } else { header.lh_offset as u64 }; // Parse out the filename. let filename = detect_filename(filename_basic, header.flags.filename_unicode, extra_fields.as_ref()); Ok(Entry::CentralDirectoryEntry(CentralDirectoryEntry { header, compressed_size, uncompressed_size, lh_offset, filename, })) } } astral_async_zip-0.0.17/src/base/read/counting.rs000064400000000000000000000033301046102023000200650ustar 00000000000000use std::io; use std::io::Read; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::AsyncBufRead; use futures_lite::io::AsyncRead; /// A wrapper around a reader that counts the number of bytes read. pub struct Counting { inner: R, bytes: u64, } impl Counting { /// Creates a new [`Counting`] reader that wraps the provided inner reader. pub fn new(inner: R) -> Self { Self { inner, bytes: 0 } } /// Returns the number of bytes read so far. pub fn bytes_read(&self) -> u64 { self.bytes } /// Consumes the [`Counting`] reader and returns the inner reader. pub fn into_inner(self) -> R { self.inner } } impl Read for Counting { fn read(&mut self, buf: &mut [u8]) -> io::Result { let n = self.inner.read(buf)?; self.bytes += n as u64; Ok(n) } } impl AsyncRead for Counting { fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { let this = self.get_mut(); match Pin::new(&mut this.inner).poll_read(cx, buf) { Poll::Ready(Ok(n)) => { this.bytes += n as u64; Poll::Ready(Ok(n)) } other => other, } } } impl AsyncBufRead for Counting { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); Pin::new(&mut this.inner).poll_fill_buf(cx) } fn consume(self: Pin<&mut Self>, amt: usize) { let this = self.get_mut(); this.bytes += amt as u64; Pin::new(&mut this.inner).consume(amt); } } astral_async_zip-0.0.17/src/base/read/io/combined_record.rs000064400000000000000000000062341046102023000217720ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // Copyright (c) 2023 Cognite AS // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::spec::header::{EndOfCentralDirectoryHeader, Zip64EndOfCentralDirectoryRecord}; /// Combines all the fields in EOCDR and Zip64EOCDR into one struct. #[derive(Debug)] pub struct CombinedCentralDirectoryRecord { pub version_made_by: Option, pub version_needed_to_extract: Option, pub disk_number: u32, pub disk_number_start_of_cd: u32, pub num_entries_in_directory_on_disk: u64, pub num_entries_in_directory: u64, pub directory_size: u64, pub offset_of_start_of_directory: u64, pub file_comment_length: u16, } impl CombinedCentralDirectoryRecord { /// Combine an EOCDR with an optional Zip64EOCDR. /// /// Fields that are set to their max value in the EOCDR will be overwritten by the contents of /// the corresponding Zip64EOCDR field. pub fn combine(eocdr: EndOfCentralDirectoryHeader, zip64eocdr: Zip64EndOfCentralDirectoryRecord) -> Self { let mut combined = Self::from(&eocdr); if eocdr.disk_num == u16::MAX { combined.disk_number = zip64eocdr.disk_number; } if eocdr.start_cent_dir_disk == u16::MAX { combined.disk_number_start_of_cd = zip64eocdr.disk_number_start_of_cd; } if eocdr.num_of_entries_disk == u16::MAX { combined.num_entries_in_directory_on_disk = zip64eocdr.num_entries_in_directory_on_disk; } if eocdr.num_of_entries == u16::MAX { combined.num_entries_in_directory = zip64eocdr.num_entries_in_directory; } if eocdr.size_cent_dir == u32::MAX { combined.directory_size = zip64eocdr.directory_size; } if eocdr.cent_dir_offset == u32::MAX { combined.offset_of_start_of_directory = zip64eocdr.offset_of_start_of_directory; } combined.version_made_by = Some(zip64eocdr.version_made_by); combined.version_needed_to_extract = Some(zip64eocdr.version_needed_to_extract); combined } /// Returns the offset of the start of the central directory in bytes. pub fn central_directory_offset(&self) -> u64 { self.offset_of_start_of_directory } /// Returns the number of entries in the central directory. pub fn num_entries(&self) -> u64 { self.num_entries_in_directory } } // An implementation for the case of no zip64EOCDR. impl From<&EndOfCentralDirectoryHeader> for CombinedCentralDirectoryRecord { fn from(header: &EndOfCentralDirectoryHeader) -> Self { Self { version_made_by: None, version_needed_to_extract: None, disk_number: header.disk_num as u32, disk_number_start_of_cd: header.start_cent_dir_disk as u32, num_entries_in_directory_on_disk: header.num_of_entries_disk as u64, num_entries_in_directory: header.num_of_entries as u64, directory_size: header.size_cent_dir as u64, offset_of_start_of_directory: header.cent_dir_offset as u64, file_comment_length: header.file_comm_length, } } } astral_async_zip-0.0.17/src/base/read/io/compressed.rs000064400000000000000000000113621046102023000210160ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::spec::Compression; use std::pin::Pin; use std::task::{Context, Poll}; #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] use async_compression::futures::bufread; use futures_lite::io::{AsyncBufRead, AsyncRead}; use pin_project::pin_project; /// A wrapping reader which holds concrete types for all respective compression method readers. #[pin_project(project = CompressedReaderProj)] pub(crate) enum CompressedReader { Stored(#[pin] R), #[cfg(feature = "deflate")] Deflate(#[pin] bufread::DeflateDecoder), #[cfg(feature = "deflate64")] Deflate64(#[pin] bufread::Deflate64Decoder), #[cfg(feature = "bzip2")] Bz(#[pin] bufread::BzDecoder), #[cfg(feature = "lzma")] Lzma(#[pin] bufread::LzmaDecoder), #[cfg(feature = "zstd")] Zstd(#[pin] bufread::ZstdDecoder), #[cfg(feature = "xz")] Xz(#[pin] bufread::XzDecoder), } impl CompressedReader where R: AsyncBufRead + Unpin, { /// Constructs a new wrapping reader from a generic [`AsyncBufRead`] implementer. pub(crate) fn new(reader: R, compression: Compression) -> Self { match compression { Compression::Stored => CompressedReader::Stored(reader), #[cfg(feature = "deflate")] Compression::Deflate => CompressedReader::Deflate(bufread::DeflateDecoder::new(reader)), #[cfg(feature = "deflate64")] Compression::Deflate64 => CompressedReader::Deflate64(bufread::Deflate64Decoder::new(reader)), #[cfg(feature = "bzip2")] Compression::Bz => CompressedReader::Bz(bufread::BzDecoder::new(reader)), #[cfg(feature = "lzma")] Compression::Lzma => CompressedReader::Lzma(bufread::LzmaDecoder::new(reader)), #[cfg(feature = "zstd")] Compression::Zstd => CompressedReader::Zstd(bufread::ZstdDecoder::new(reader)), #[cfg(feature = "xz")] Compression::Xz => CompressedReader::Xz(bufread::XzDecoder::new(reader)), } } /// Consumes this reader and returns the inner value. pub(crate) fn inner(&self) -> &R { match self { CompressedReader::Stored(inner) => inner, #[cfg(feature = "deflate")] CompressedReader::Deflate(inner) => inner.get_ref(), #[cfg(feature = "deflate64")] CompressedReader::Deflate64(inner) => inner.get_ref(), #[cfg(feature = "bzip2")] CompressedReader::Bz(inner) => inner.get_ref(), #[cfg(feature = "lzma")] CompressedReader::Lzma(inner) => inner.get_ref(), #[cfg(feature = "zstd")] CompressedReader::Zstd(inner) => inner.get_ref(), #[cfg(feature = "xz")] CompressedReader::Xz(inner) => inner.get_ref(), } } /// Consumes this reader and returns the inner value. pub(crate) fn into_inner(self) -> R { match self { CompressedReader::Stored(inner) => inner, #[cfg(feature = "deflate")] CompressedReader::Deflate(inner) => inner.into_inner(), #[cfg(feature = "deflate64")] CompressedReader::Deflate64(inner) => inner.into_inner(), #[cfg(feature = "bzip2")] CompressedReader::Bz(inner) => inner.into_inner(), #[cfg(feature = "lzma")] CompressedReader::Lzma(inner) => inner.into_inner(), #[cfg(feature = "zstd")] CompressedReader::Zstd(inner) => inner.into_inner(), #[cfg(feature = "xz")] CompressedReader::Xz(inner) => inner.into_inner(), } } } impl AsyncRead for CompressedReader where R: AsyncBufRead + Unpin, { fn poll_read(self: Pin<&mut Self>, c: &mut Context<'_>, b: &mut [u8]) -> Poll> { match self.project() { CompressedReaderProj::Stored(inner) => inner.poll_read(c, b), #[cfg(feature = "deflate")] CompressedReaderProj::Deflate(inner) => inner.poll_read(c, b), #[cfg(feature = "deflate64")] CompressedReaderProj::Deflate64(inner) => inner.poll_read(c, b), #[cfg(feature = "bzip2")] CompressedReaderProj::Bz(inner) => inner.poll_read(c, b), #[cfg(feature = "lzma")] CompressedReaderProj::Lzma(inner) => inner.poll_read(c, b), #[cfg(feature = "zstd")] CompressedReaderProj::Zstd(inner) => inner.poll_read(c, b), #[cfg(feature = "xz")] CompressedReaderProj::Xz(inner) => inner.poll_read(c, b), } } } astral_async_zip-0.0.17/src/base/read/io/entry.rs000064400000000000000000000111261046102023000200110ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::read::counting::Counting; use crate::base::read::io::{compressed::CompressedReader, hashed::HashedReader, owned::OwnedReader}; use crate::entry::ZipEntry; use crate::error::{Result, ZipError}; use crate::spec::Compression; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::{AsyncBufRead, AsyncRead, AsyncReadExt, Take}; use pin_project::pin_project; /// A type which encodes that [`ZipEntryReader`] has associated entry data. pub struct WithEntry<'a>(OwnedEntry<'a>); /// A type which encodes that [`ZipEntryReader`] has no associated entry data. pub struct WithoutEntry; /// A ZIP entry reader which may implement decompression. #[pin_project] pub struct ZipEntryReader<'a, R, E> { #[pin] reader: HashedReader>>>>, entry: E, } impl<'a, R> ZipEntryReader<'a, R, WithoutEntry> where R: AsyncBufRead + Unpin, { /// Constructs a new entry reader from its required parameters (incl. an owned R). pub(crate) fn new_with_owned(reader: R, compression: Compression, size: u64) -> Self { let reader = HashedReader::new(CompressedReader::new(Counting::new(OwnedReader::Owned(reader).take(size)), compression)); Self { reader, entry: WithoutEntry } } /// Constructs a new entry reader from its required parameters (incl. a mutable borrow of an R). pub(crate) fn new_with_borrow(reader: &'a mut R, compression: Compression, size: u64) -> Self { let reader = HashedReader::new(CompressedReader::new( Counting::new(OwnedReader::Borrow(reader).take(size)), compression, )); Self { reader, entry: WithoutEntry } } pub(crate) fn into_with_entry(self, entry: &'a ZipEntry) -> ZipEntryReader<'a, R, WithEntry<'a>> { ZipEntryReader { reader: self.reader, entry: WithEntry(OwnedEntry::Borrow(entry)) } } pub(crate) fn into_with_entry_owned(self, entry: ZipEntry) -> ZipEntryReader<'a, R, WithEntry<'a>> { ZipEntryReader { reader: self.reader, entry: WithEntry(OwnedEntry::Owned(entry)) } } } impl<'a, R, E> AsyncRead for ZipEntryReader<'a, R, E> where R: AsyncBufRead + Unpin, { fn poll_read(self: Pin<&mut Self>, c: &mut Context<'_>, b: &mut [u8]) -> Poll> { self.project().reader.poll_read(c, b) } } impl<'a, R, E> ZipEntryReader<'a, R, E> where R: AsyncBufRead + Unpin, { /// Computes and returns the CRC32 hash of bytes read by this reader so far. /// /// This hash should only be computed once EOF has been reached. pub fn compute_hash(&mut self) -> u32 { self.reader.swap_and_compute_hash() } /// Return the number of bytes read so far by this reader. pub fn bytes_read(&self) -> u64 { self.reader.inner().inner().bytes_read() } /// Consumes this reader and returns the inner value. pub(crate) fn into_inner(self) -> R { self.reader.into_inner().into_inner().into_inner().into_inner().owned_into_inner() } } impl ZipEntryReader<'_, R, WithEntry<'_>> where R: AsyncBufRead + Unpin, { /// Returns an immutable reference to the associated entry data. pub fn entry(&self) -> &'_ ZipEntry { self.entry.0.entry() } /// Reads all bytes until EOF has been reached, appending them to buf, and verifies the CRC32 values. /// /// This is a helper function synonymous to [`AsyncReadExt::read_to_end()`]. pub async fn read_to_end_checked(&mut self, buf: &mut Vec) -> Result { let read = self.read_to_end(buf).await?; if self.compute_hash() == self.entry.0.entry().crc32() { Ok(read) } else { Err(ZipError::CRC32CheckError) } } /// Reads all bytes until EOF has been reached, placing them into buf, and verifies the CRC32 values. /// /// This is a helper function synonymous to [`AsyncReadExt::read_to_string()`]. pub async fn read_to_string_checked(&mut self, buf: &mut String) -> Result { let read = self.read_to_string(buf).await?; if self.compute_hash() == self.entry.0.entry().crc32() { Ok(read) } else { Err(ZipError::CRC32CheckError) } } } enum OwnedEntry<'a> { Owned(ZipEntry), Borrow(&'a ZipEntry), } impl<'a> OwnedEntry<'a> { pub fn entry(&self) -> &'_ ZipEntry { match self { OwnedEntry::Owned(entry) => entry, OwnedEntry::Borrow(entry) => entry, } } } astral_async_zip-0.0.17/src/base/read/io/hashed.rs000064400000000000000000000035361046102023000201120ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::read::io::poll_result_ok; use std::pin::Pin; use std::task::{ready, Context, Poll}; use crc32fast::Hasher; use futures_lite::io::AsyncRead; use pin_project::pin_project; /// A wrapping reader which computes the CRC32 hash of data read via [`AsyncRead`]. #[pin_project] pub(crate) struct HashedReader { #[pin] pub(crate) reader: R, pub(crate) hasher: Hasher, } impl HashedReader where R: AsyncRead + Unpin, { /// Constructs a new wrapping reader from a generic [`AsyncRead`] implementer. pub(crate) fn new(reader: R) -> Self { Self { reader, hasher: Hasher::default() } } /// Swaps the internal hasher and returns the computed CRC32 hash. /// /// The internal hasher is taken and replaced with a newly-constructed one. As a result, this method should only be /// called once EOF has been reached and it's known that no more data will be read, else the computed hash(s) won't /// accurately represent the data read in. pub(crate) fn swap_and_compute_hash(&mut self) -> u32 { std::mem::take(&mut self.hasher).finalize() } /// Returns a reference to the inner reader. pub(crate) fn inner(&self) -> &R { &self.reader } /// Consumes this reader and returns the inner value. pub(crate) fn into_inner(self) -> R { self.reader } } impl AsyncRead for HashedReader where R: AsyncRead + Unpin, { fn poll_read(self: Pin<&mut Self>, c: &mut Context<'_>, b: &mut [u8]) -> Poll> { let project = self.project(); let written = poll_result_ok!(ready!(project.reader.poll_read(c, b))); project.hasher.update(&b[..written]); Poll::Ready(Ok(written)) } } astral_async_zip-0.0.17/src/base/read/io/locator.rs000064400000000000000000000114271046102023000203170ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! //! //! As with other ZIP libraries, we face the predicament that the end of central directory record may contain a //! variable-length file comment. As a result, we cannot just make the assumption that the start of this record is //! 18 bytes (the length of the EOCDR) offset from the end of the data - we must locate it ourselves. //! //! The `zip-rs` crate handles this by reading in reverse from the end of the data. This involves seeking backwards //! by a single byte each iteration and reading 4 bytes into a u32. Whether this is performant/acceptable within a //! a non-async context, I'm unsure, but it isn't desirable within an async context. Especially since we cannot just //! place a [`BufReader`] infront of the upstream reader (as its internal buffer is invalidated on each seek). //! //! Reading in reverse is still desirable as the use of file comments is limited and they're unlikely to be large. //! //! The below method is one that compromises on these two contention points. Please submit an issue or PR if you know //! of a better algorithm for this (and have tested/verified its performance). #[cfg(doc)] use futures_lite::io::BufReader; use crate::error::{Result as ZipResult, ZipError}; use crate::spec::consts::{EOCDR_LENGTH, EOCDR_SIGNATURE, SIGNATURE_LENGTH}; use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom}; /// The buffer size used when locating the EOCDR, equal to 2KiB. const BUFFER_SIZE: usize = 2048; /// The upper bound of where the EOCDR signature cannot be located. const EOCDR_UPPER_BOUND: u64 = EOCDR_LENGTH as u64; /// The lower bound of where the EOCDR signature cannot be located. const EOCDR_LOWER_BOUND: u64 = EOCDR_UPPER_BOUND + SIGNATURE_LENGTH as u64 + u16::MAX as u64; /// Locate the `end of central directory record` offset, if one exists. /// The returned offset excludes the signature (4 bytes) /// /// This method involves buffered reading in reverse and reverse linear searching along those buffers for the EOCDR /// signature. As a result of this buffered approach, we reduce seeks when compared to `zip-rs`'s method by a factor /// of the buffer size. We also then don't have to do individual u32 reads against the upstream reader. /// /// Whilst I haven't done any in-depth benchmarks, when reading a ZIP file with the maximum length comment, this method /// saw a reduction in location time by a factor of 500 when compared with the `zip-rs` method. pub async fn eocdr(mut reader: R) -> ZipResult where R: AsyncRead + AsyncSeek + Unpin, { let length = reader.seek(SeekFrom::End(0)).await?; let signature = &EOCDR_SIGNATURE.to_le_bytes(); let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut position = length.saturating_sub((EOCDR_LENGTH + BUFFER_SIZE) as u64); reader.seek(SeekFrom::Start(position)).await?; loop { let read = reader.read(&mut buffer).await?; if let Some(match_index) = reverse_search_buffer(&buffer[..read], signature) { return Ok(position + (match_index + 1) as u64); } // If we hit the start of the data or the lower bound, we're unable to locate the EOCDR. if position == 0 || position <= length.saturating_sub(EOCDR_LOWER_BOUND) { return Err(ZipError::UnableToLocateEOCDR); } // To handle the case where the EOCDR signature crosses buffer boundaries, we simply overlap reads by the // signature length. This significantly reduces the complexity of handling partial matches with very little // overhead. position = position.saturating_sub((BUFFER_SIZE - SIGNATURE_LENGTH) as u64); reader.seek(SeekFrom::Start(position)).await?; } } /// A naive reverse linear search along the buffer for the specified signature bytes. /// /// This is already surprisingly performant. For instance, using memchr::memchr() to match for the first byte of the /// signature, and then manual byte comparisons for the remaining signature bytes was actually slower by a factor of /// 2.25. This method was explored as tokio's `read_until()` implementation uses memchr::memchr(). pub(crate) fn reverse_search_buffer(buffer: &[u8], signature: &[u8]) -> Option { 'outer: for index in (0..buffer.len()).rev() { for (signature_index, signature_byte) in signature.iter().rev().enumerate() { if let Some(next_index) = index.checked_sub(signature_index) { if buffer[next_index] != *signature_byte { continue 'outer; } } else { break 'outer; } } return Some(index); } None } astral_async_zip-0.0.17/src/base/read/io/mod.rs000064400000000000000000000034771046102023000174410ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod combined_record; pub(crate) mod compressed; pub(crate) mod entry; pub(crate) mod hashed; pub(crate) mod locator; pub(crate) mod owned; pub use combined_record::CombinedCentralDirectoryRecord; use crate::string::{StringEncoding, ZipString}; use futures_lite::io; use futures_lite::io::{AsyncRead, AsyncReadExt}; /// Read and return a dynamic length string from a reader which impls AsyncRead. pub(crate) async fn read_string(reader: R, length: usize, encoding: StringEncoding) -> std::io::Result where R: AsyncRead + Unpin, { Ok(ZipString::new(read_bytes(reader, length).await?, encoding)) } /// Read and return a dynamic length vector of bytes from a reader which impls AsyncRead. pub(crate) async fn read_bytes(reader: R, length: usize) -> std::io::Result> where R: AsyncRead + Unpin, { let mut buffer = Vec::with_capacity(length); reader.take(length as u64).read_to_end(&mut buffer).await?; Ok(buffer) } /// Skip a specified number of bytes in an AsyncRead implementer. pub(crate) async fn skip_bytes(reader: R, length: u64) -> std::io::Result<()> where R: AsyncRead + Unpin, { let mut sink = io::sink(); io::copy(&mut reader.take(length), &mut sink).await?; Ok(()) } /// A macro that returns the inner value of an Ok or early-returns in the case of an Err. /// /// This is almost identical to the ? operator but handles the situation when a Result is used in combination with /// Poll (eg. tokio's IO traits such as AsyncRead). macro_rules! poll_result_ok { ($poll:expr) => { match $poll { Ok(inner) => inner, Err(err) => return Poll::Ready(Err(err)), } }; } use poll_result_ok; astral_async_zip-0.0.17/src/base/read/io/owned.rs000064400000000000000000000037051046102023000177700ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::{AsyncBufRead, AsyncRead}; use pin_project::pin_project; /// A wrapping reader which holds an owned R or a mutable borrow to R. /// /// This is used to represent whether the supplied reader can be acted on concurrently or not (with an owned value /// suggesting that R implements some method of synchronisation & cloning). #[pin_project(project = OwnedReaderProj)] pub(crate) enum OwnedReader<'a, R> { Owned(#[pin] R), Borrow(#[pin] &'a mut R), } impl<'a, R> OwnedReader<'a, R> where R: AsyncBufRead + Unpin, { /// Consumes an owned reader and returns the inner value. pub(crate) fn owned_into_inner(self) -> R { match self { OwnedReader::Owned(inner) => inner, OwnedReader::Borrow(_) => panic!("not OwnedReader::Owned value"), } } } impl<'a, R> AsyncBufRead for OwnedReader<'a, R> where R: AsyncBufRead + Unpin, { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project() { OwnedReaderProj::Owned(inner) => inner.poll_fill_buf(cx), OwnedReaderProj::Borrow(inner) => inner.poll_fill_buf(cx), } } fn consume(self: Pin<&mut Self>, amt: usize) { match self.project() { OwnedReaderProj::Owned(inner) => inner.consume(amt), OwnedReaderProj::Borrow(inner) => inner.consume(amt), } } } impl<'a, R> AsyncRead for OwnedReader<'a, R> where R: AsyncBufRead + Unpin, { fn poll_read(self: Pin<&mut Self>, c: &mut Context<'_>, b: &mut [u8]) -> Poll> { match self.project() { OwnedReaderProj::Owned(inner) => inner.poll_read(c, b), OwnedReaderProj::Borrow(inner) => inner.poll_read(c, b), } } } astral_async_zip-0.0.17/src/base/read/mem.rs000064400000000000000000000117531046102023000170250ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A concurrent ZIP reader which acts over an owned vector of bytes. //! //! Concurrency is achieved as a result of: //! - Wrapping the provided vector of bytes within an [`Arc`] to allow shared ownership. //! - Wrapping this [`Arc`] around a [`Cursor`] when reading (as the [`Arc`] can deref and coerce into a `&[u8]`). //! //! ### Usage //! Unlike the [`seek`] module, we no longer hold a mutable reference to any inner reader which in turn, allows the //! construction of concurrent [`ZipEntryReader`]s. Though, note that each individual [`ZipEntryReader`] cannot be sent //! between thread boundaries due to the masked lifetime requirement. Therefore, the overarching [`ZipFileReader`] //! should be cloned and moved into those contexts when needed. //! //! ### Concurrent Example //! ```no_run //! # use async_zip::base::read::mem::ZipFileReader; //! # use async_zip::error::Result; //! # use futures_lite::io::AsyncReadExt; //! # //! async fn run() -> Result<()> { //! let reader = ZipFileReader::new(Vec::new()).await?; //! let result = tokio::join!(read(&reader, 0), read(&reader, 1)); //! //! let data_0 = result.0?; //! let data_1 = result.1?; //! //! // Use data within current scope. //! //! Ok(()) //! } //! //! async fn read(reader: &ZipFileReader, index: usize) -> Result> { //! let mut entry = reader.reader_without_entry(index).await?; //! let mut data = Vec::new(); //! entry.read_to_end(&mut data).await?; //! Ok(data) //! } //! ``` //! //! ### Parallel Example //! ```no_run //! # use async_zip::base::read::mem::ZipFileReader; //! # use async_zip::error::Result; //! # use futures_lite::io::AsyncReadExt; //! # //! async fn run() -> Result<()> { //! let reader = ZipFileReader::new(Vec::new()).await?; //! //! let handle_0 = tokio::spawn(read(reader.clone(), 0)); //! let handle_1 = tokio::spawn(read(reader.clone(), 1)); //! //! let data_0 = handle_0.await.expect("thread panicked")?; //! let data_1 = handle_1.await.expect("thread panicked")?; //! //! // Use data within current scope. //! //! Ok(()) //! } //! //! async fn read(reader: ZipFileReader, index: usize) -> Result> { //! let mut entry = reader.reader_without_entry(index).await?; //! let mut data = Vec::new(); //! entry.read_to_end(&mut data).await?; //! Ok(data) //! } //! ``` #[cfg(doc)] use crate::base::read::seek; use crate::base::read::io::entry::ZipEntryReader; use crate::error::{Result, ZipError}; use crate::file::ZipFile; use std::sync::Arc; use futures_lite::io::Cursor; use super::io::entry::{WithEntry, WithoutEntry}; struct Inner { data: Vec, file: ZipFile, } // A concurrent ZIP reader which acts over an owned vector of bytes. #[derive(Clone)] pub struct ZipFileReader { inner: Arc, } impl ZipFileReader { /// Constructs a new ZIP reader from an owned vector of bytes. pub async fn new(data: Vec) -> Result { let file = crate::base::read::file(Cursor::new(&data)).await?; Ok(ZipFileReader::from_raw_parts(data, file)) } /// Constructs a ZIP reader from an owned vector of bytes and ZIP file information derived from those bytes. /// /// Providing a [`ZipFile`] that wasn't derived from those bytes may lead to inaccurate parsing. pub fn from_raw_parts(data: Vec, file: ZipFile) -> ZipFileReader { ZipFileReader { inner: Arc::new(Inner { data, file }) } } /// Returns this ZIP file's information. pub fn file(&self) -> &ZipFile { &self.inner.file } /// Returns the raw bytes provided to the reader during construction. pub fn data(&self) -> &[u8] { &self.inner.data } /// Returns a new entry reader if the provided index is valid. pub async fn reader_without_entry(&self, index: usize) -> Result, WithoutEntry>> { let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; let mut cursor = Cursor::new(&self.inner.data[..]); stored_entry.seek_to_data_offset(&mut cursor).await?; Ok(ZipEntryReader::new_with_owned( cursor, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), )) } /// Returns a new entry reader if the provided index is valid. pub async fn reader_with_entry(&self, index: usize) -> Result, WithEntry<'_>>> { let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; let mut cursor = Cursor::new(&self.inner.data[..]); stored_entry.seek_to_data_offset(&mut cursor).await?; let reader = ZipEntryReader::new_with_owned( cursor, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), ); Ok(reader.into_with_entry(stored_entry)) } } astral_async_zip-0.0.17/src/base/read/mod.rs000064400000000000000000000313171046102023000170240ustar 00000000000000// Copyright (c) 2022-2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A module which supports reading ZIP files. pub mod mem; pub mod seek; pub mod stream; pub mod cd; mod counting; pub(crate) mod io; use crate::ZipString; // Re-exported as part of the public API. pub use crate::base::read::io::entry::WithEntry; pub use crate::base::read::io::entry::WithoutEntry; pub use crate::base::read::io::entry::ZipEntryReader; use crate::date::ZipDateTime; use crate::entry::{StoredZipEntry, ZipEntry}; use crate::error::{Result, ZipError}; use crate::file::ZipFile; use crate::spec::attribute::AttributeCompatibility; use crate::spec::consts::LFH_LENGTH; use crate::spec::consts::{CDH_SIGNATURE, LFH_SIGNATURE, NON_ZIP64_MAX_SIZE, SIGNATURE_LENGTH, ZIP64_EOCDL_LENGTH}; use crate::spec::header::InfoZipUnicodeCommentExtraField; use crate::spec::header::InfoZipUnicodePathExtraField; use crate::spec::header::{ CentralDirectoryRecord, EndOfCentralDirectoryHeader, ExtraField, LocalFileHeader, Zip64EndOfCentralDirectoryLocator, Zip64EndOfCentralDirectoryRecord, Zip64ExtendedInformationExtraField, }; use crate::spec::Compression; use crate::string::StringEncoding; use crate::base::read::io::CombinedCentralDirectoryRecord; use crate::spec::parse::parse_extra_fields; use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, BufReader, SeekFrom}; /// The max buffer size used when parsing the central directory, equal to 20MiB. const MAX_CD_BUFFER_SIZE: usize = 20 * 1024 * 1024; pub(crate) async fn file(mut reader: R) -> Result where R: AsyncRead + AsyncSeek + Unpin, { // First find and parse the EOCDR. let eocdr_offset = crate::base::read::io::locator::eocdr(&mut reader).await?; reader.seek(SeekFrom::Start(eocdr_offset)).await?; let eocdr = EndOfCentralDirectoryHeader::from_reader(&mut reader).await?; let comment = io::read_string(&mut reader, eocdr.file_comm_length.into(), crate::StringEncoding::Utf8).await?; // Check the 20 bytes before the EOCDR for the Zip64 EOCDL, plus an extra 4 bytes because the offset // does not include the signature. If the ECODL exists we are dealing with a Zip64 file. let (eocdr, zip64) = match eocdr_offset.checked_sub(ZIP64_EOCDL_LENGTH + SIGNATURE_LENGTH as u64) { None => (CombinedCentralDirectoryRecord::from(&eocdr), false), Some(offset) => { reader.seek(SeekFrom::Start(offset)).await?; let zip64_locator = Zip64EndOfCentralDirectoryLocator::try_from_reader(&mut reader).await?; match zip64_locator { Some(locator) => { reader.seek(SeekFrom::Start(locator.relative_offset + SIGNATURE_LENGTH as u64)).await?; let zip64_eocdr = Zip64EndOfCentralDirectoryRecord::from_reader(&mut reader).await?; (CombinedCentralDirectoryRecord::combine(eocdr, zip64_eocdr), true) } None => (CombinedCentralDirectoryRecord::from(&eocdr), false), } } }; // Outdated feature so unlikely to ever make it into this crate. if eocdr.disk_number != eocdr.disk_number_start_of_cd || eocdr.num_entries_in_directory != eocdr.num_entries_in_directory_on_disk { return Err(ZipError::FeatureNotSupported("Spanned/split files")); } // Find and parse the central directory. reader.seek(SeekFrom::Start(eocdr.offset_of_start_of_directory)).await?; // To avoid lots of small reads to `reader` when parsing the central directory, we use a BufReader that can read the whole central directory at once. // Because `eocdr.offset_of_start_of_directory` is a u64, we use MAX_CD_BUFFER_SIZE to prevent very large buffer sizes. let buf = BufReader::with_capacity(std::cmp::min(eocdr.offset_of_start_of_directory as _, MAX_CD_BUFFER_SIZE), reader); let entries = crate::base::read::cd(buf, eocdr.num_entries_in_directory, zip64).await?; Ok(ZipFile { entries, comment, zip64 }) } pub(crate) async fn cd(mut reader: R, num_of_entries: u64, zip64: bool) -> Result> where R: AsyncRead + Unpin, { let num_of_entries = num_of_entries.try_into().map_err(|_| ZipError::TargetZip64NotSupported)?; let mut entries = Vec::with_capacity(num_of_entries); for _ in 0..num_of_entries { let entry = cd_record(&mut reader, zip64).await?; entries.push(entry); } Ok(entries) } pub(crate) fn get_zip64_extra_field(extra_fields: &[ExtraField]) -> Option<&Zip64ExtendedInformationExtraField> { for field in extra_fields { if let ExtraField::Zip64ExtendedInformation(zip64field) = field { return Some(zip64field); } } None } fn get_combined_sizes( uncompressed_size: u32, compressed_size: u32, extra_field: &Option<&Zip64ExtendedInformationExtraField>, ) -> Result<(u64, u64)> { let mut uncompressed_size = uncompressed_size as u64; let mut compressed_size = compressed_size as u64; if let Some(extra_field) = extra_field { if let Some(s) = extra_field.uncompressed_size { if uncompressed_size == NON_ZIP64_MAX_SIZE as u64 { uncompressed_size = s; } } if let Some(s) = extra_field.compressed_size { if compressed_size == NON_ZIP64_MAX_SIZE as u64 { compressed_size = s; } } } Ok((uncompressed_size, compressed_size)) } pub(crate) async fn cd_record(mut reader: R, _zip64: bool) -> Result where R: AsyncRead + Unpin, { crate::utils::assert_signature(&mut reader, CDH_SIGNATURE).await?; let header = CentralDirectoryRecord::from_reader(&mut reader).await?; let header_size = (SIGNATURE_LENGTH + LFH_LENGTH) as u64; let trailing_size = header.file_name_length as u64 + header.extra_field_length as u64; let filename_basic = io::read_bytes(&mut reader, header.file_name_length.into()).await?; let compression = Compression::try_from(header.compression)?; let extra_field = io::read_bytes(&mut reader, header.extra_field_length.into()).await?; let extra_fields = parse_extra_fields( extra_field, header.uncompressed_size, header.compressed_size, Some(header.lh_offset), Some(header.disk_start), )?; let comment_basic = io::read_bytes(reader, header.file_comment_length.into()).await?; let zip64_extra_field = get_zip64_extra_field(&extra_fields); let (uncompressed_size, compressed_size) = get_combined_sizes(header.uncompressed_size, header.compressed_size, &zip64_extra_field)?; let mut file_offset = header.lh_offset as u64; if let Some(zip64_extra_field) = zip64_extra_field { if file_offset == NON_ZIP64_MAX_SIZE as u64 { if let Some(offset) = zip64_extra_field.relative_header_offset { file_offset = offset; } } } let filename = detect_filename(filename_basic, header.flags.filename_unicode, extra_fields.as_ref()); let comment = detect_comment(comment_basic, header.flags.filename_unicode, extra_fields.as_ref()); let entry = ZipEntry { filename, compression, #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] compression_level: async_compression::Level::Default, attribute_compatibility: AttributeCompatibility::Unix, // FIXME: Default to Unix for the moment crc32: header.crc, uncompressed_size, compressed_size, last_modification_date: ZipDateTime { date: header.mod_date, time: header.mod_time }, internal_file_attribute: header.inter_attr, external_file_attribute: header.exter_attr, extra_fields, comment, data_descriptor: header.flags.data_descriptor, file_offset, }; Ok(StoredZipEntry { entry, file_offset, header_size: header_size + trailing_size }) } pub(crate) async fn lfh(mut reader: R, file_offset: u64) -> Result> where R: AsyncRead + Unpin, { let signature = { let mut buffer = [0; 4]; reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; match signature { actual if actual == LFH_SIGNATURE => (), actual if actual == CDH_SIGNATURE => return Ok(None), actual => return Err(ZipError::UnexpectedHeaderError(actual, LFH_SIGNATURE)), }; let header = LocalFileHeader::from_reader(&mut reader).await?; let filename_basic = io::read_bytes(&mut reader, header.file_name_length.into()).await?; let compression = Compression::try_from(header.compression)?; let extra_field = io::read_bytes(&mut reader, header.extra_field_length.into()).await?; let extra_fields = parse_extra_fields(extra_field, header.uncompressed_size, header.compressed_size, None, None)?; let zip64_extra_field = get_zip64_extra_field(&extra_fields); let (uncompressed_size, compressed_size) = get_combined_sizes(header.uncompressed_size, header.compressed_size, &zip64_extra_field)?; if header.flags.data_descriptor && compression == Compression::Stored { return Err(ZipError::FeatureNotSupported( "stream reading entries with data descriptors & Stored compression mode", )); } if header.flags.encrypted { return Err(ZipError::FeatureNotSupported("encryption")); } let filename = detect_filename(filename_basic, header.flags.filename_unicode, extra_fields.as_ref()); let entry = ZipEntry { filename, compression, #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] compression_level: async_compression::Level::Default, attribute_compatibility: AttributeCompatibility::Unix, // FIXME: Default to Unix for the moment crc32: header.crc, uncompressed_size, compressed_size, last_modification_date: ZipDateTime { date: header.mod_date, time: header.mod_time }, internal_file_attribute: 0, external_file_attribute: 0, extra_fields, comment: String::new().into(), data_descriptor: header.flags.data_descriptor, file_offset, }; Ok(Some(entry)) } fn detect_comment(basic: Vec, basic_is_utf8: bool, extra_fields: &[ExtraField]) -> ZipString { if basic_is_utf8 { ZipString::new(basic, StringEncoding::Utf8) } else { let unicode_extra = extra_fields.iter().find_map(|field| match field { ExtraField::InfoZipUnicodeComment(InfoZipUnicodeCommentExtraField::V1 { crc32, unicode }) => { if *crc32 == crc32fast::hash(&basic) { Some(std::string::String::from_utf8(unicode.clone())) } else { None } } _ => None, }); if let Some(Ok(s)) = unicode_extra { ZipString::new_with_alternative(s, basic) } else { // Do not treat as UTF-8 if UTF-8 flags are not set, // some string in MBCS may be valid UTF-8 in form, but they are not in truth. if basic.is_ascii() { // SAFETY: // a valid ASCII string is always a valid UTF-8 string unsafe { std::string::String::from_utf8_unchecked(basic).into() } } else { ZipString::new(basic, StringEncoding::Raw) } } } } fn detect_filename(basic: Vec, basic_is_utf8: bool, extra_fields: &[ExtraField]) -> ZipString { let unicode_extra = extra_fields.iter().find_map(|field| match field { ExtraField::InfoZipUnicodePath(InfoZipUnicodePathExtraField::V1 { crc32, unicode }) => { if !unicode.is_empty() && *crc32 == crc32fast::hash(&basic) { Some(std::string::String::from_utf8(unicode.clone())) } else { None } } _ => None, }); if let Some(Ok(s)) = unicode_extra { ZipString::new_with_alternative(s, basic) } else if basic_is_utf8 { ZipString::new(basic, StringEncoding::Utf8) } else { // Do not treat as UTF-8 if UTF-8 flags are not set, // some string in MBCS may be valid UTF-8 in form, but they are not in truth. if basic.is_ascii() { // SAFETY: // a valid ASCII string is always a valid UTF-8 string unsafe { std::string::String::from_utf8_unchecked(basic).into() } } else { ZipString::new(basic, StringEncoding::Raw) } } } astral_async_zip-0.0.17/src/base/read/seek.rs000064400000000000000000000111101046102023000171610ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A ZIP reader which acts over a seekable source. //! //! ### Example //! ```no_run //! # use async_zip::base::read::seek::ZipFileReader; //! # use async_zip::error::Result; //! # use futures_lite::io::AsyncReadExt; //! # use tokio::fs::File; //! # use tokio_util::compat::TokioAsyncReadCompatExt; //! # use tokio::io::BufReader; //! # //! async fn run() -> Result<()> { //! let mut data = BufReader::new(File::open("./foo.zip").await?); //! let mut reader = ZipFileReader::new(data.compat()).await?; //! //! let mut data = Vec::new(); //! let mut entry = reader.reader_without_entry(0).await?; //! entry.read_to_end(&mut data).await?; //! //! // Use data within current scope. //! //! Ok(()) //! } //! ``` use crate::base::read::io::entry::ZipEntryReader; use crate::error::{Result, ZipError}; use crate::file::ZipFile; #[cfg(feature = "tokio")] use crate::tokio::read::seek::ZipFileReader as TokioZipFileReader; use futures_lite::io::{AsyncBufRead, AsyncSeek}; #[cfg(feature = "tokio")] use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; use super::io::entry::{WithEntry, WithoutEntry}; /// A ZIP reader which acts over a seekable source. #[derive(Clone)] pub struct ZipFileReader { reader: R, file: ZipFile, } impl ZipFileReader where R: AsyncBufRead + AsyncSeek + Unpin, { /// Constructs a new ZIP reader from a seekable source. pub async fn new(mut reader: R) -> Result> { let file = crate::base::read::file(&mut reader).await?; Ok(ZipFileReader::from_raw_parts(reader, file)) } /// Constructs a ZIP reader from a seekable source and ZIP file information derived from that source. /// /// Providing a [`ZipFile`] that wasn't derived from that source may lead to inaccurate parsing. pub fn from_raw_parts(reader: R, file: ZipFile) -> ZipFileReader { ZipFileReader { reader, file } } /// Returns this ZIP file's information. pub fn file(&self) -> &ZipFile { &self.file } /// Returns a mutable reference to the inner seekable source. /// /// Swapping the source (eg. via std::mem operations) may lead to inaccurate parsing. pub fn inner_mut(&mut self) -> &mut R { &mut self.reader } /// Returns the inner seekable source by consuming self. pub fn into_inner(self) -> R { self.reader } /// Returns a new entry reader if the provided index is valid. pub async fn reader_without_entry(&mut self, index: usize) -> Result> { let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; stored_entry.seek_to_data_offset(&mut self.reader).await?; Ok(ZipEntryReader::new_with_borrow( &mut self.reader, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), )) } /// Returns a new entry reader if the provided index is valid. pub async fn reader_with_entry(&mut self, index: usize) -> Result>> { let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; stored_entry.seek_to_data_offset(&mut self.reader).await?; let reader = ZipEntryReader::new_with_borrow( &mut self.reader, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), ); Ok(reader.into_with_entry(stored_entry)) } /// Returns a new entry reader if the provided index is valid. /// Consumes self pub async fn into_entry<'a>(mut self, index: usize) -> Result> where R: 'a, { let stored_entry = self.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; stored_entry.seek_to_data_offset(&mut self.reader).await?; Ok(ZipEntryReader::new_with_owned( self.reader, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), )) } } #[cfg(feature = "tokio")] impl ZipFileReader> where R: tokio::io::AsyncBufRead + tokio::io::AsyncSeek + Unpin, { /// Constructs a new tokio-specific ZIP reader from a seekable source. pub async fn with_tokio(reader: R) -> Result> { let mut reader = reader.compat(); let file = crate::base::read::file(&mut reader).await?; Ok(ZipFileReader::from_raw_parts(reader, file)) } } astral_async_zip-0.0.17/src/base/read/stream.rs000064400000000000000000000202541046102023000175360ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A ZIP reader which acts over a non-seekable source. //! //! # API Design //! As opposed to other readers provided by this crate, it's important that the data of an entry is fully read before //! the proceeding entry is read. This is as a result of not being able to seek forwards or backwards, so we must end //! up at the start of the next entry. //! //! **We encode this invariant within Rust's type system so that it can be enforced at compile time.** //! //! This requires that any transition methods between these encoded types consume the reader and provide a new owned //! reader back. This is certainly something to keep in mind when working with this reader, but idiomatic code can //! still be produced nevertheless. //! //! # Considerations //! As the central directory of a ZIP archive is stored at the end of it, a non-seekable reader doesn't have access //! to it. We have to rely on information provided within the local file header which may not be accurate or complete. //! This results in: //! - The inability to read ZIP entries using the combination of a data descriptor and the Stored compression method. //! - No file comment being available (defaults to an empty string). //! - No internal or external file attributes being available (defaults to 0). //! - The extra field data potentially being inconsistent with what's stored in the central directory. //! - None of the following being available when the entry was written with a data descriptor (defaults to 0): //! - CRC //! - compressed size //! - uncompressed size //! //! # Example //! ```no_run //! # use futures_lite::io::Cursor; //! # use async_zip::error::Result; //! # use async_zip::base::read::stream::ZipFileReader; //! # //! # async fn run() -> Result<()> { //! let mut zip = ZipFileReader::new(Cursor::new([0; 0])); //! //! // Print the name of every file in a ZIP archive. //! while let Some(entry) = zip.next_with_entry().await? { //! println!("File: {}", entry.reader().entry().filename().as_str().unwrap()); //! (.., zip) = entry.skip().await?; //! } //! # //! # Ok(()) //! # } //! ``` use crate::base::read::counting::Counting; use crate::base::read::io::entry::ZipEntryReader; use crate::error::Result; use crate::error::ZipError; use crate::spec::data_descriptor::{CombinedDataDescriptor, DataDescriptor, Zip64DataDescriptor}; #[cfg(feature = "tokio")] use crate::tokio::read::stream::Ready as TokioReady; use futures_lite::io::AsyncBufRead; use futures_lite::io::AsyncReadExt; use super::io::entry::WithEntry; use super::io::entry::WithoutEntry; use crate::spec::header::HeaderId; #[cfg(feature = "tokio")] use tokio_util::compat::TokioAsyncReadCompatExt; /// A type which encodes that [`ZipFileReader`] is ready to open a new entry. pub struct Ready(R); /// A type which encodes that [`ZipFileReader`] is currently reading an entry. pub struct Reading<'a, R, E>(ZipEntryReader<'a, R, E>, Option); #[derive(Copy, Clone, Debug)] enum Suffix { /// The entry is followed by a data descriptor. DataDescriptor, /// The entry is followed by a ZIP64 data descriptor. Zip64DataDescriptor, } /// A ZIP reader which acts over a non-seekable source. /// /// See the [module-level docs](.) for more information. #[derive(Clone)] pub struct ZipFileReader(S); impl<'a, R> ZipFileReader>> where R: AsyncBufRead + Unpin + 'a, { /// Constructs a new ZIP reader from a non-seekable source. pub fn new(reader: R) -> Self { Self(Ready(Counting::new(reader))) } /// Opens the next entry for reading if the central directory hasn’t yet been reached. pub async fn next_without_entry(mut self) -> Result, WithoutEntry>>>> { let file_offset = self.0 .0.bytes_read(); let entry = match crate::base::read::lfh(&mut self.0 .0, file_offset).await? { Some(entry) => entry, None => return Ok(None), }; let length = if entry.data_descriptor { u64::MAX } else { entry.compressed_size }; let reader = ZipEntryReader::new_with_owned(self.0 .0, entry.compression, length); let suffix = if entry.data_descriptor { if entry.extra_fields.iter().any(|ef| ef.header_id() == HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD) { Some(Suffix::Zip64DataDescriptor) } else { Some(Suffix::DataDescriptor) } } else { None }; Ok(Some(ZipFileReader(Reading(reader, suffix)))) } /// Opens the next entry for reading if the central directory hasn’t yet been reached. pub async fn next_with_entry(mut self) -> Result, WithEntry<'a>>>>> { let file_offset = self.0 .0.bytes_read(); let entry = match crate::base::read::lfh(&mut self.0 .0, file_offset).await? { Some(entry) => entry, None => return Ok(None), }; let length = if entry.data_descriptor { u64::MAX } else { entry.compressed_size }; let reader = ZipEntryReader::new_with_owned(self.0 .0, entry.compression, length); let suffix = if entry.data_descriptor { if entry.extra_fields.iter().any(|ef| ef.header_id() == HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD) { Some(Suffix::Zip64DataDescriptor) } else { Some(Suffix::DataDescriptor) } } else { None }; Ok(Some(ZipFileReader(Reading(reader.into_with_entry_owned(entry), suffix)))) } /// Consumes the `ZipFileReader` returning the original `reader` pub async fn into_inner(self) -> R { self.0 .0.into_inner() } /// Returns the file offset of the current reader. pub fn offset(&self) -> u64 { self.0 .0.bytes_read() } } #[cfg(feature = "tokio")] impl ZipFileReader> where R: tokio::io::AsyncBufRead + Unpin, { /// Constructs a new tokio-specific ZIP reader from a non-seekable source. pub fn with_tokio(reader: R) -> ZipFileReader> { Self(Ready(reader.compat())) } } type Next = (Option, ZipFileReader>); impl<'a, R, E> ZipFileReader> where R: AsyncBufRead + Unpin, { /// Returns an immutable reference to the inner entry reader. pub fn reader(&self) -> &ZipEntryReader<'a, R, E> { &self.0 .0 } /// Returns a mutable reference to the inner entry reader. pub fn reader_mut(&mut self) -> &mut ZipEntryReader<'a, R, E> { &mut self.0 .0 } /// Converts the reader back into the Ready state if EOF has been reached. pub async fn done(mut self) -> Result> { if self.0 .0.read(&mut [0; 1]).await? != 0 { return Err(ZipError::EOFNotReached); } let mut inner = self.0 .0.into_inner(); let data_descriptor = match self.0 .1 { Some(Suffix::DataDescriptor) => { Some(CombinedDataDescriptor::from(DataDescriptor::from_reader(&mut inner).await?)) } Some(Suffix::Zip64DataDescriptor) => { Some(CombinedDataDescriptor::from(Zip64DataDescriptor::from_reader(&mut inner).await?)) } None => None, }; let reader = ZipFileReader(Ready(inner)); Ok((data_descriptor, reader)) } /// Reads until EOF and converts the reader back into the Ready state. pub async fn skip(mut self) -> Result> { while self.0 .0.read(&mut [0; 2048]).await? != 0 {} let mut inner = self.0 .0.into_inner(); let data_descriptor = match self.0 .1 { Some(Suffix::DataDescriptor) => { Some(CombinedDataDescriptor::from(DataDescriptor::from_reader(&mut inner).await?)) } Some(Suffix::Zip64DataDescriptor) => { Some(CombinedDataDescriptor::from(Zip64DataDescriptor::from_reader(&mut inner).await?)) } None => None, }; let reader = ZipFileReader(Ready(inner)); Ok((data_descriptor, reader)) } } astral_async_zip-0.0.17/src/date/mod.rs000064400000000000000000000025671046102023000161210ustar 00000000000000// Copyright (c) 2021-2024 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#446 // https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-dosdatetimetovarianttime /// A date and time stored as per the MS-DOS representation used by ZIP files. #[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)] pub struct ZipDateTime { pub(crate) date: u16, pub(crate) time: u16, } impl ZipDateTime { /// Returns the year of this date & time. pub fn year(&self) -> i32 { (((self.date & 0xFE00) >> 9) + 1980).into() } /// Returns the month of this date & time. pub fn month(&self) -> u32 { ((self.date & 0x1E0) >> 5).into() } /// Returns the day of this date & time. pub fn day(&self) -> u32 { (self.date & 0x1F).into() } /// Returns the hour of this date & time. pub fn hour(&self) -> u32 { ((self.time & 0xF800) >> 11).into() } /// Returns the minute of this date & time. pub fn minute(&self) -> u32 { ((self.time & 0x7E0) >> 5).into() } /// Returns the second of this date & time. /// /// Note that MS-DOS has a maximum granularity of two seconds. pub fn second(&self) -> u32 { ((self.time & 0x1F) << 1).into() } } astral_async_zip-0.0.17/src/entry/mod.rs000064400000000000000000000140551046102023000163400ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use std::ops::Deref; use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom}; use crate::error::{Result, ZipError}; use crate::spec::{ attribute::AttributeCompatibility, consts::LFH_SIGNATURE, header::{ExtraField, LocalFileHeader}, Compression, }; use crate::{string::ZipString, ZipDateTime}; /// An immutable store of data about a ZIP entry. #[derive(Clone, Debug)] pub struct ZipEntry { pub(crate) filename: ZipString, pub(crate) compression: Compression, #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] pub(crate) compression_level: async_compression::Level, pub(crate) crc32: u32, pub(crate) uncompressed_size: u64, pub(crate) compressed_size: u64, pub(crate) attribute_compatibility: AttributeCompatibility, pub(crate) last_modification_date: ZipDateTime, pub(crate) internal_file_attribute: u16, pub(crate) external_file_attribute: u32, pub(crate) extra_fields: Vec, pub(crate) comment: ZipString, pub(crate) data_descriptor: bool, pub(crate) file_offset: u64, } impl ZipEntry { /// Returns the entry's filename. /// /// ## Note /// This will return the raw filename stored during ZIP creation. If calling this method on entries retrieved from /// untrusted ZIP files, the filename should be sanitised before being used as a path to prevent [directory /// traversal attacks](https://en.wikipedia.org/wiki/Directory_traversal_attack). pub fn filename(&self) -> &ZipString { &self.filename } /// Returns the entry's compression method. pub fn compression(&self) -> Compression { self.compression } /// Returns the entry's CRC32 value. pub fn crc32(&self) -> u32 { self.crc32 } /// Returns the entry's uncompressed size. pub fn uncompressed_size(&self) -> u64 { self.uncompressed_size } /// Returns the entry's compressed size. pub fn compressed_size(&self) -> u64 { self.compressed_size } /// Returns the entry's attribute's host compatibility. pub fn attribute_compatibility(&self) -> AttributeCompatibility { self.attribute_compatibility } /// Returns the entry's last modification time & date. pub fn last_modification_date(&self) -> &ZipDateTime { &self.last_modification_date } /// Returns the entry's internal file attribute. pub fn internal_file_attribute(&self) -> u16 { self.internal_file_attribute } /// Returns the entry's external file attribute pub fn external_file_attribute(&self) -> u32 { self.external_file_attribute } /// Returns the entry's extra field data. pub fn extra_fields(&self) -> &[ExtraField] { &self.extra_fields } /// Returns the entry's file comment. pub fn comment(&self) -> &ZipString { &self.comment } /// Returns the entry's integer-based UNIX permissions. /// /// # Note /// This will return None if the attribute host compatibility is not listed as Unix. pub fn unix_permissions(&self) -> Option { if !matches!(self.attribute_compatibility, AttributeCompatibility::Unix) { return None; } Some(((self.external_file_attribute) >> 16) as u16) } /// Returns whether or not the entry represents a directory. pub fn dir(&self) -> Result { Ok(self.filename.as_str()?.ends_with('/')) } /// Returns whether or not the entry has a data descriptor. pub fn data_descriptor(&self) -> bool { self.data_descriptor } /// Returns the file offset in bytes of the local file header for this entry. pub fn file_offset(&self) -> u64 { self.file_offset } } /// An immutable store of data about how a ZIP entry is stored within a specific archive. /// /// Besides storing archive independent information like the size and timestamp it can also be used to query /// information about how the entry is stored in an archive. #[derive(Clone)] pub struct StoredZipEntry { pub(crate) entry: ZipEntry, // pub(crate) general_purpose_flag: GeneralPurposeFlag, pub(crate) file_offset: u64, pub(crate) header_size: u64, } impl StoredZipEntry { /// Returns the offset in bytes to where the header of the entry starts. pub fn header_offset(&self) -> u64 { self.file_offset } /// Returns the combined size in bytes of the header, the filename, and any extra fields. /// /// Note: This uses the extra field length stored in the central directory, which may differ from that stored in /// the local file header. See specification: pub fn header_size(&self) -> u64 { self.header_size } /// Seek to the offset in bytes where the data of the entry starts. pub(crate) async fn seek_to_data_offset(&self, mut reader: &mut R) -> Result<()> { // Seek to the header reader.seek(SeekFrom::Start(self.file_offset)).await?; // Check the signature let signature = { let mut buffer = [0; 4]; reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; match signature { LFH_SIGNATURE => (), actual => return Err(ZipError::UnexpectedHeaderError(actual, LFH_SIGNATURE)), }; // Skip the local file header and trailing data let header = LocalFileHeader::from_reader(&mut reader).await?; let trailing_size = (header.file_name_length as i64) + (header.extra_field_length as i64); reader.seek(SeekFrom::Current(trailing_size)).await?; Ok(()) } } impl Deref for StoredZipEntry { type Target = ZipEntry; fn deref(&self) -> &Self::Target { &self.entry } } astral_async_zip-0.0.17/src/error.rs000064400000000000000000000065701046102023000155540ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A module which holds relevant error reporting structures/types. use std::fmt::{Display, Formatter}; use thiserror::Error; /// A Result type alias over ZipError to minimise repetition. pub type Result = std::result::Result; #[derive(Debug, PartialEq, Eq)] pub enum Zip64ErrorCase { TooManyFiles, LargeFile, } impl Display for Zip64ErrorCase { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::TooManyFiles => write!(f, "More than 65536 files in archive"), Self::LargeFile => write!(f, "File is larger than 4 GiB"), } } } /// An enum of possible errors and their descriptions. #[non_exhaustive] #[derive(Debug, Error)] pub enum ZipError { #[error("feature not supported: '{0}'")] FeatureNotSupported(&'static str), #[error("compression not supported: {0}")] CompressionNotSupported(u16), #[error("host attribute compatibility not supported: {0}")] AttributeCompatibilityNotSupported(u16), #[error("attempted to read a ZIP64 file whilst on a 32-bit target")] TargetZip64NotSupported, #[error("attempted to write a ZIP file with force_no_zip64 when ZIP64 is needed: {0}")] Zip64Needed(Zip64ErrorCase), #[error("end of file has not been reached")] EOFNotReached, #[error("extra fields exceeded maximum size")] ExtraFieldTooLarge, #[error("comment exceeded maximum size")] CommentTooLarge, #[error("filename exceeded maximum size")] FileNameTooLarge, #[error("attempted to convert non-UTF8 bytes to a string/str")] StringNotUtf8, #[error("unable to locate the end of central directory record")] UnableToLocateEOCDR, #[error("extra field size was indicated to be {0} but fewer than {0} bytes remain")] InvalidExtraFieldHeader(u16), #[error("zip64 extended information field was incomplete")] Zip64ExtendedFieldIncomplete, #[error("an extra field with id {0:#x} was duplicated in the header")] DuplicateExtraFieldHeader(u16), #[error("an upstream reader returned an error: {0}")] UpstreamReadError(#[from] std::io::Error), #[error("a computed CRC32 value did not match the expected value")] CRC32CheckError, #[error("entry index was out of bounds")] EntryIndexOutOfBounds, #[error("Encountered an unexpected header (actual: {0:#x}, expected: {1:#x}).")] UnexpectedHeaderError(u32, u32), #[error("Info-ZIP Unicode Comment Extra Field was incomplete")] InfoZipUnicodeCommentFieldIncomplete, #[error("Info-ZIP Unicode Path Extra Field was incomplete")] InfoZipUnicodePathFieldIncomplete, #[error("the end of central directory offset ({0:#x}) did not match the actual offset ({1:#x})")] InvalidEndOfCentralDirectoryOffset(u64, u64), #[error("the zip64 end of central directory locator was not found")] MissingZip64EndOfCentralDirectoryLocator, #[error("the zip64 end of central directory locator offset ({0:#x}) did not match the actual offset ({1:#x})")] InvalidZip64EndOfCentralDirectoryLocatorOffset(u64, u64), #[error( "zip64 extended information field was too long: expected {expected} bytes, but {actual} bytes were provided" )] Zip64ExtendedInformationFieldTooLong { expected: usize, actual: usize }, } astral_async_zip-0.0.17/src/file/mod.rs000064400000000000000000000013771046102023000161210ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::{entry::StoredZipEntry, string::ZipString}; /// An immutable store of data about a ZIP file. #[derive(Clone)] pub struct ZipFile { pub(crate) entries: Vec, pub(crate) zip64: bool, pub(crate) comment: ZipString, } impl ZipFile { /// Returns a list of this ZIP file's entries. pub fn entries(&self) -> &[StoredZipEntry] { &self.entries } /// Returns this ZIP file's trailing comment. pub fn comment(&self) -> &ZipString { &self.comment } /// Returns whether or not this ZIP file is zip64 pub fn zip64(&self) -> bool { self.zip64 } } astral_async_zip-0.0.17/src/lib.rs000064400000000000000000000042771046102023000151730ustar 00000000000000// Copyright (c) 2021-2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) // Document all features on docs.rs #![cfg_attr(docsrs, feature(doc_cfg))] //! An asynchronous ZIP archive reading/writing crate. //! //! ## Features //! - A base implementation atop `futures`'s IO traits. //! - An extended implementation atop `tokio`'s IO traits. //! - Support for Stored, Deflate, bzip2, LZMA, zstd, and xz compression methods. //! - Various different reading approaches (seek, stream, filesystem, in-memory buffer). //! - Support for writing complete data (u8 slices) or stream writing using data descriptors. //! - Initial support for ZIP64 reading and writing. //! - Aims for reasonable [specification](https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md) compliance. //! //! ## Installation //! //! ```toml //! [dependencies] //! async_zip = { version = "0.0.17", features = ["full"] } //! ``` //! //! ### Feature Flags //! - `full` - Enables all below features. //! - `full-wasm` - Enables all below features that are compatible with WASM. //! - `chrono` - Enables support for parsing dates via `chrono`. //! - `tokio` - Enables support for the `tokio` implementation module. //! - `tokio-fs` - Enables support for the `tokio::fs` reading module. //! - `deflate` - Enables support for the Deflate compression method. //! - `bzip2` - Enables support for the bzip2 compression method. //! - `lzma` - Enables support for the LZMA compression method. //! - `zstd` - Enables support for the zstd compression method. //! - `xz` - Enables support for the xz compression method. //! //! [Read more.](https://github.com/Majored/rs-async-zip) pub mod base; pub mod error; #[cfg(feature = "tokio")] pub mod tokio; pub(crate) mod date; pub(crate) mod entry; pub(crate) mod file; pub(crate) mod spec; pub(crate) mod string; pub(crate) mod utils; #[cfg(test)] pub(crate) mod tests; pub use crate::spec::attribute::AttributeCompatibility; pub use crate::spec::compression::{Compression, DeflateOption}; pub use crate::date::ZipDateTime; pub use crate::entry::{StoredZipEntry, ZipEntry}; pub use crate::file::ZipFile; pub use crate::string::{StringEncoding, ZipString}; astral_async_zip-0.0.17/src/spec/attribute.rs000064400000000000000000000026551046102023000173600ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::error::{Result, ZipError}; /// An attribute host compatibility supported by this crate. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AttributeCompatibility { Unix, } impl TryFrom for AttributeCompatibility { type Error = ZipError; // Convert a u16 stored with little endianness into a supported attribute host compatibility. // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4422 fn try_from(value: u16) -> Result { match value { 3 => Ok(AttributeCompatibility::Unix), _ => Err(ZipError::AttributeCompatibilityNotSupported(value)), } } } impl From<&AttributeCompatibility> for u16 { // Convert a supported attribute host compatibility into its relevant u16 stored with little endianness. // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4422 fn from(compatibility: &AttributeCompatibility) -> Self { match compatibility { AttributeCompatibility::Unix => 3, } } } impl From for u16 { // Convert a supported attribute host compatibility into its relevant u16 stored with little endianness. fn from(compatibility: AttributeCompatibility) -> Self { (&compatibility).into() } } astral_async_zip-0.0.17/src/spec/compression.rs000064400000000000000000000054101046102023000177060ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::error::{Result, ZipError}; /// A compression method supported by this crate. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Compression { Stored, #[cfg(feature = "deflate")] Deflate, #[cfg(feature = "deflate64")] Deflate64, #[cfg(feature = "bzip2")] Bz, #[cfg(feature = "lzma")] Lzma, #[cfg(feature = "zstd")] Zstd, #[cfg(feature = "xz")] Xz, } impl TryFrom for Compression { type Error = ZipError; // Convert a u16 stored with little endianness into a supported compression method. // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#445 fn try_from(value: u16) -> Result { match value { 0 => Ok(Compression::Stored), #[cfg(feature = "deflate")] 8 => Ok(Compression::Deflate), #[cfg(feature = "deflate64")] 9 => Ok(Compression::Deflate64), #[cfg(feature = "bzip2")] 12 => Ok(Compression::Bz), #[cfg(feature = "lzma")] 14 => Ok(Compression::Lzma), #[cfg(feature = "zstd")] 93 => Ok(Compression::Zstd), #[cfg(feature = "xz")] 95 => Ok(Compression::Xz), _ => Err(ZipError::CompressionNotSupported(value)), } } } impl From<&Compression> for u16 { // Convert a supported compression method into its relevant u16 stored with little endianness. // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#445 fn from(compression: &Compression) -> u16 { match compression { Compression::Stored => 0, #[cfg(feature = "deflate")] Compression::Deflate => 8, #[cfg(feature = "deflate64")] Compression::Deflate64 => 9, #[cfg(feature = "bzip2")] Compression::Bz => 12, #[cfg(feature = "lzma")] Compression::Lzma => 14, #[cfg(feature = "zstd")] Compression::Zstd => 93, #[cfg(feature = "xz")] Compression::Xz => 95, } } } impl From for u16 { fn from(compression: Compression) -> u16 { (&compression).into() } } /// Level of compression data should be compressed with for deflate. #[derive(Debug, Clone, Copy)] pub enum DeflateOption { // Normal (-en) compression option was used. Normal, // Maximum (-exx/-ex) compression option was used. Maximum, // Fast (-ef) compression option was used. Fast, // Super Fast (-es) compression option was used. Super, /// Other implementation defined level. Other(i32), } astral_async_zip-0.0.17/src/spec/consts.rs000064400000000000000000000033361046102023000166630ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub const SIGNATURE_LENGTH: usize = 4; // Local file header constants // // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#437 pub const LFH_SIGNATURE: u32 = 0x4034b50; #[allow(dead_code)] pub const LFH_LENGTH: usize = 26; // Central directory header constants // // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4312 pub const CDH_SIGNATURE: u32 = 0x2014b50; #[allow(dead_code)] pub const CDH_LENGTH: usize = 42; // End of central directory record constants // // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4316 pub const EOCDR_SIGNATURE: u32 = 0x6054b50; /// The minimum length of the EOCDR, excluding the signature. pub const EOCDR_LENGTH: usize = 18; /// The signature for the zip64 end of central directory record. /// Ref: https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4314 pub const ZIP64_EOCDR_SIGNATURE: u32 = 0x06064b50; /// The signature for the zip64 end of central directory locator. /// Ref: https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4315 pub const ZIP64_EOCDL_SIGNATURE: u32 = 0x07064b50; /// The length of the ZIP64 EOCDL, including the signature. /// The EOCDL has a fixed size, thankfully. pub const ZIP64_EOCDL_LENGTH: u64 = 20; /// The contents of a header field when one must reference the zip64 version instead. pub const NON_ZIP64_MAX_SIZE: u32 = 0xFFFFFFFF; // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#439 pub const DATA_DESCRIPTOR_SIGNATURE: u32 = 0x8074b50; pub const DATA_DESCRIPTOR_LENGTH: usize = 12; pub const ZIP64_DATA_DESCRIPTOR_LENGTH: usize = 20; astral_async_zip-0.0.17/src/spec/data_descriptor.rs000064400000000000000000000021361046102023000205160ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub struct DataDescriptor { pub crc: u32, pub compressed_size: u32, pub uncompressed_size: u32, } pub struct Zip64DataDescriptor { pub crc: u32, pub compressed_size: u64, pub uncompressed_size: u64, } pub struct CombinedDataDescriptor { pub crc: u32, pub compressed_size: u64, pub uncompressed_size: u64, } impl From for CombinedDataDescriptor { fn from(descriptor: DataDescriptor) -> Self { CombinedDataDescriptor { crc: descriptor.crc, compressed_size: descriptor.compressed_size as u64, uncompressed_size: descriptor.uncompressed_size as u64, } } } impl From for CombinedDataDescriptor { fn from(descriptor: Zip64DataDescriptor) -> Self { CombinedDataDescriptor { crc: descriptor.crc, compressed_size: descriptor.compressed_size, uncompressed_size: descriptor.uncompressed_size, } } } astral_async_zip-0.0.17/src/spec/extra_field.rs000064400000000000000000000141111046102023000176310ustar 00000000000000// Copyright Cognite AS, 2023 use crate::error::{Result as ZipResult, ZipError}; use crate::spec::header::{ ExtraField, HeaderId, InfoZipUnicodeCommentExtraField, InfoZipUnicodePathExtraField, UnknownExtraField, Zip64ExtendedInformationExtraField, }; use super::consts::NON_ZIP64_MAX_SIZE; /// Parse a zip64 extra field from bytes. /// The content of "data" should exclude the header. fn zip64_extended_information_field_from_bytes( _header_id: HeaderId, data: &[u8], header_uncompressed_size: u32, header_compressed_size: u32, header_relative_header_offset: Option, header_disk_start_number: Option, ) -> ZipResult { // slice.take is nightly-only so we'll just use an index to track the current position let mut current_idx = 0; let uncompressed_size = if header_uncompressed_size == NON_ZIP64_MAX_SIZE && data.len() >= current_idx + 8 { let val = Some(u64::from_le_bytes(data[current_idx..current_idx + 8].try_into().unwrap())); current_idx += 8; val } else { None }; let compressed_size = if header_compressed_size == NON_ZIP64_MAX_SIZE && data.len() >= current_idx + 8 { let val = Some(u64::from_le_bytes(data[current_idx..current_idx + 8].try_into().unwrap())); current_idx += 8; val } else { None }; let relative_header_offset = if header_relative_header_offset == Some(NON_ZIP64_MAX_SIZE) && data.len() >= current_idx + 8 { let val = Some(u64::from_le_bytes(data[current_idx..current_idx + 8].try_into().unwrap())); current_idx += 8; val } else { None }; #[allow(unused_assignments)] let disk_start_number = if header_disk_start_number == Some(0xFFFF) && data.len() >= current_idx + 4 { let val = Some(u32::from_le_bytes(data[current_idx..current_idx + 4].try_into().unwrap())); current_idx += 4; val } else { None }; if current_idx != data.len() { // In some cases, we've seen zips that include the zip64 extended information field with // uncompressed and compressed sizes equal to the local header sizes. We accept these, even // though they are not strictly compliant with the spec. if current_idx == 0 && data.len() == 16 { let uncompressed_size = u64::from_le_bytes(data[current_idx..current_idx + 8].try_into().unwrap()); let compressed_size = u64::from_le_bytes(data[current_idx + 8..current_idx + 16].try_into().unwrap()); if uncompressed_size == header_uncompressed_size as u64 && compressed_size == header_compressed_size as u64 && header_relative_header_offset.is_none() && header_disk_start_number.is_none() { return Ok(Zip64ExtendedInformationExtraField { uncompressed_size: Some(uncompressed_size), compressed_size: Some(compressed_size), relative_header_offset: None, disk_start_number: None, }); } } return Err(ZipError::Zip64ExtendedInformationFieldTooLong { expected: data.len(), actual: current_idx }); } Ok(Zip64ExtendedInformationExtraField { uncompressed_size, compressed_size, relative_header_offset, disk_start_number, }) } fn info_zip_unicode_comment_extra_field_from_bytes( _header_id: HeaderId, data_size: u16, data: &[u8], ) -> ZipResult { if data.is_empty() { return Err(ZipError::InfoZipUnicodeCommentFieldIncomplete); } let version = data[0]; match version { 1 => { if data.len() < 5 { return Err(ZipError::InfoZipUnicodeCommentFieldIncomplete); } let crc32 = u32::from_le_bytes(data[1..5].try_into().unwrap()); let unicode = data[5..(data_size as usize)].to_vec(); Ok(InfoZipUnicodeCommentExtraField::V1 { crc32, unicode }) } _ => Ok(InfoZipUnicodeCommentExtraField::Unknown { version, data: data[1..(data_size as usize)].to_vec() }), } } fn info_zip_unicode_path_extra_field_from_bytes( _header_id: HeaderId, data_size: u16, data: &[u8], ) -> ZipResult { if data.is_empty() { return Err(ZipError::InfoZipUnicodePathFieldIncomplete); } let version = data[0]; match version { 1 => { if data.len() < 5 { return Err(ZipError::InfoZipUnicodePathFieldIncomplete); } let crc32 = u32::from_le_bytes(data[1..5].try_into().unwrap()); let unicode = data[5..(data_size as usize)].to_vec(); Ok(InfoZipUnicodePathExtraField::V1 { crc32, unicode }) } _ => Ok(InfoZipUnicodePathExtraField::Unknown { version, data: data[1..(data_size as usize)].to_vec() }), } } pub(crate) fn extra_field_from_bytes( header_id: HeaderId, data_size: u16, data: &[u8], uncompressed_size: u32, compressed_size: u32, relative_header_offset: Option, disk_start_number: Option, ) -> ZipResult { match header_id { HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD => { Ok(ExtraField::Zip64ExtendedInformation(zip64_extended_information_field_from_bytes( header_id, data, uncompressed_size, compressed_size, relative_header_offset, disk_start_number, )?)) } HeaderId::INFO_ZIP_UNICODE_COMMENT_EXTRA_FIELD => Ok(ExtraField::InfoZipUnicodeComment( info_zip_unicode_comment_extra_field_from_bytes(header_id, data_size, data)?, )), HeaderId::INFO_ZIP_UNICODE_PATH_EXTRA_FIELD => Ok(ExtraField::InfoZipUnicodePath( info_zip_unicode_path_extra_field_from_bytes(header_id, data_size, data)?, )), _ => Ok(ExtraField::Unknown(UnknownExtraField { header_id, data_size, content: data.to_vec() })), } } astral_async_zip-0.0.17/src/spec/header.rs000064400000000000000000000135171046102023000166040ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#437 pub struct LocalFileHeader { pub version: u16, pub flags: GeneralPurposeFlag, pub compression: u16, pub mod_time: u16, pub mod_date: u16, pub crc: u32, pub compressed_size: u32, pub uncompressed_size: u32, pub file_name_length: u16, pub extra_field_length: u16, } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#444 #[derive(Copy, Clone)] pub struct GeneralPurposeFlag { pub encrypted: bool, pub data_descriptor: bool, pub filename_unicode: bool, } /// 2 byte header ids /// Ref https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#452 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct HeaderId(pub u16); impl HeaderId { pub const ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD: HeaderId = HeaderId(0x0001); pub const INFO_ZIP_UNICODE_COMMENT_EXTRA_FIELD: HeaderId = HeaderId(0x6375); pub const INFO_ZIP_UNICODE_PATH_EXTRA_FIELD: HeaderId = HeaderId(0x7075); } impl From for HeaderId { fn from(value: u16) -> Self { HeaderId(value) } } impl From for u16 { fn from(value: HeaderId) -> Self { value.0 } } /// Represents each extra field. /// Not strictly part of the spec, but is the most useful way to represent the data. #[derive(Clone, Debug)] #[non_exhaustive] pub enum ExtraField { Zip64ExtendedInformation(Zip64ExtendedInformationExtraField), InfoZipUnicodeComment(InfoZipUnicodeCommentExtraField), InfoZipUnicodePath(InfoZipUnicodePathExtraField), Unknown(UnknownExtraField), } impl ExtraField { /// Returns the [`HeaderId`] of the extra field. pub fn header_id(&self) -> HeaderId { match self { ExtraField::Zip64ExtendedInformation(..) => HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD, ExtraField::InfoZipUnicodeComment(..) => HeaderId::INFO_ZIP_UNICODE_COMMENT_EXTRA_FIELD, ExtraField::InfoZipUnicodePath(..) => HeaderId::INFO_ZIP_UNICODE_PATH_EXTRA_FIELD, ExtraField::Unknown(field) => field.header_id, } } } /// An extended information header for Zip64. /// This field is used both for local file headers and central directory records. /// https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#453 #[derive(Clone, Debug)] pub struct Zip64ExtendedInformationExtraField { pub uncompressed_size: Option, pub compressed_size: Option, // While not specified in the spec, these two fields are often left out in practice. pub relative_header_offset: Option, pub disk_start_number: Option, } /// Stores the UTF-8 version of the file comment as stored in the central directory header. /// https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#468 #[derive(Clone, Debug)] pub enum InfoZipUnicodeCommentExtraField { V1 { crc32: u32, unicode: Vec }, Unknown { version: u8, data: Vec }, } /// Stores the UTF-8 version of the file name field as stored in the local header and central directory header. /// https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#469 #[derive(Clone, Debug)] pub enum InfoZipUnicodePathExtraField { V1 { crc32: u32, unicode: Vec }, Unknown { version: u8, data: Vec }, } /// Represents any unparsed extra field. #[derive(Clone, Debug)] pub struct UnknownExtraField { pub header_id: HeaderId, pub data_size: u16, pub content: Vec, } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4312 pub struct CentralDirectoryRecord { pub v_made_by: u16, pub v_needed: u16, pub flags: GeneralPurposeFlag, pub compression: u16, pub mod_time: u16, pub mod_date: u16, pub crc: u32, pub compressed_size: u32, pub uncompressed_size: u32, pub file_name_length: u16, pub extra_field_length: u16, pub file_comment_length: u16, pub disk_start: u16, pub inter_attr: u16, pub exter_attr: u32, pub lh_offset: u32, } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4316 #[derive(Debug)] pub struct EndOfCentralDirectoryHeader { pub(crate) disk_num: u16, pub(crate) start_cent_dir_disk: u16, pub(crate) num_of_entries_disk: u16, pub(crate) num_of_entries: u16, pub(crate) size_cent_dir: u32, pub(crate) cent_dir_offset: u32, pub(crate) file_comm_length: u16, } impl EndOfCentralDirectoryHeader { /// Returns the offset of the start of the central directory in bytes. pub fn central_directory_offset(&self) -> u64 { self.cent_dir_offset as u64 } /// Returns the number of entries in the central directory. pub fn num_entries(&self) -> u64 { self.num_of_entries as u64 } } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4314 #[derive(Debug, PartialEq)] pub struct Zip64EndOfCentralDirectoryRecord { /// The size of this Zip64EndOfCentralDirectoryRecord. /// This is specified because there is a variable-length extra zip64 information sector. /// However, we will gleefully ignore this sector because it is reserved for use by PKWare. pub size_of_zip64_end_of_cd_record: u64, pub version_made_by: u16, pub version_needed_to_extract: u16, pub disk_number: u32, pub disk_number_start_of_cd: u32, pub num_entries_in_directory_on_disk: u64, pub num_entries_in_directory: u64, pub directory_size: u64, pub offset_of_start_of_directory: u64, } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#4315 #[derive(Debug, PartialEq)] pub struct Zip64EndOfCentralDirectoryLocator { pub number_of_disk_with_start_of_zip64_end_of_central_directory: u32, pub relative_offset: u64, pub total_number_of_disks: u32, } astral_async_zip-0.0.17/src/spec/mod.rs000064400000000000000000000005361046102023000161300ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod attribute; pub(crate) mod compression; pub(crate) mod consts; pub(crate) mod data_descriptor; pub(crate) mod extra_field; pub(crate) mod header; pub(crate) mod parse; pub use compression::Compression; astral_async_zip-0.0.17/src/spec/parse.rs000064400000000000000000000374311046102023000164670ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::error::{Result, ZipError}; use crate::spec::header::{ CentralDirectoryRecord, EndOfCentralDirectoryHeader, ExtraField, GeneralPurposeFlag, HeaderId, LocalFileHeader, Zip64EndOfCentralDirectoryLocator, Zip64EndOfCentralDirectoryRecord, }; use futures_lite::io::{AsyncRead, AsyncReadExt}; impl From<[u8; 26]> for LocalFileHeader { fn from(value: [u8; 26]) -> LocalFileHeader { LocalFileHeader { version: u16::from_le_bytes(value[0..2].try_into().unwrap()), flags: GeneralPurposeFlag::from(u16::from_le_bytes(value[2..4].try_into().unwrap())), compression: u16::from_le_bytes(value[4..6].try_into().unwrap()), mod_time: u16::from_le_bytes(value[6..8].try_into().unwrap()), mod_date: u16::from_le_bytes(value[8..10].try_into().unwrap()), crc: u32::from_le_bytes(value[10..14].try_into().unwrap()), compressed_size: u32::from_le_bytes(value[14..18].try_into().unwrap()), uncompressed_size: u32::from_le_bytes(value[18..22].try_into().unwrap()), file_name_length: u16::from_le_bytes(value[22..24].try_into().unwrap()), extra_field_length: u16::from_le_bytes(value[24..26].try_into().unwrap()), } } } impl From for GeneralPurposeFlag { fn from(value: u16) -> GeneralPurposeFlag { let encrypted = !matches!(value & 0x1, 0); let data_descriptor = !matches!((value & 0x8) >> 3, 0); let filename_unicode = !matches!((value & 0x800) >> 11, 0); GeneralPurposeFlag { encrypted, data_descriptor, filename_unicode } } } impl From<[u8; 42]> for CentralDirectoryRecord { fn from(value: [u8; 42]) -> CentralDirectoryRecord { CentralDirectoryRecord { v_made_by: u16::from_le_bytes(value[0..2].try_into().unwrap()), v_needed: u16::from_le_bytes(value[2..4].try_into().unwrap()), flags: GeneralPurposeFlag::from(u16::from_le_bytes(value[4..6].try_into().unwrap())), compression: u16::from_le_bytes(value[6..8].try_into().unwrap()), mod_time: u16::from_le_bytes(value[8..10].try_into().unwrap()), mod_date: u16::from_le_bytes(value[10..12].try_into().unwrap()), crc: u32::from_le_bytes(value[12..16].try_into().unwrap()), compressed_size: u32::from_le_bytes(value[16..20].try_into().unwrap()), uncompressed_size: u32::from_le_bytes(value[20..24].try_into().unwrap()), file_name_length: u16::from_le_bytes(value[24..26].try_into().unwrap()), extra_field_length: u16::from_le_bytes(value[26..28].try_into().unwrap()), file_comment_length: u16::from_le_bytes(value[28..30].try_into().unwrap()), disk_start: u16::from_le_bytes(value[30..32].try_into().unwrap()), inter_attr: u16::from_le_bytes(value[32..34].try_into().unwrap()), exter_attr: u32::from_le_bytes(value[34..38].try_into().unwrap()), lh_offset: u32::from_le_bytes(value[38..42].try_into().unwrap()), } } } impl From<[u8; 18]> for EndOfCentralDirectoryHeader { fn from(value: [u8; 18]) -> EndOfCentralDirectoryHeader { EndOfCentralDirectoryHeader { disk_num: u16::from_le_bytes(value[0..2].try_into().unwrap()), start_cent_dir_disk: u16::from_le_bytes(value[2..4].try_into().unwrap()), num_of_entries_disk: u16::from_le_bytes(value[4..6].try_into().unwrap()), num_of_entries: u16::from_le_bytes(value[6..8].try_into().unwrap()), size_cent_dir: u32::from_le_bytes(value[8..12].try_into().unwrap()), cent_dir_offset: u32::from_le_bytes(value[12..16].try_into().unwrap()), file_comm_length: u16::from_le_bytes(value[16..18].try_into().unwrap()), } } } impl From<[u8; 52]> for Zip64EndOfCentralDirectoryRecord { fn from(value: [u8; 52]) -> Self { Self { size_of_zip64_end_of_cd_record: u64::from_le_bytes(value[0..8].try_into().unwrap()), version_made_by: u16::from_le_bytes(value[8..10].try_into().unwrap()), version_needed_to_extract: u16::from_le_bytes(value[10..12].try_into().unwrap()), disk_number: u32::from_le_bytes(value[12..16].try_into().unwrap()), disk_number_start_of_cd: u32::from_le_bytes(value[16..20].try_into().unwrap()), num_entries_in_directory_on_disk: u64::from_le_bytes(value[20..28].try_into().unwrap()), num_entries_in_directory: u64::from_le_bytes(value[28..36].try_into().unwrap()), directory_size: u64::from_le_bytes(value[36..44].try_into().unwrap()), offset_of_start_of_directory: u64::from_le_bytes(value[44..52].try_into().unwrap()), } } } impl From<[u8; 16]> for Zip64EndOfCentralDirectoryLocator { fn from(value: [u8; 16]) -> Self { Self { number_of_disk_with_start_of_zip64_end_of_central_directory: u32::from_le_bytes( value[0..4].try_into().unwrap(), ), relative_offset: u64::from_le_bytes(value[4..12].try_into().unwrap()), total_number_of_disks: u32::from_le_bytes(value[12..16].try_into().unwrap()), } } } impl From<[u8; 16]> for DataDescriptor { fn from(value: [u8; 16]) -> Self { Self { crc: u32::from_le_bytes(value[0..4].try_into().unwrap()), compressed_size: u32::from_le_bytes(value[4..8].try_into().unwrap()), uncompressed_size: u32::from_le_bytes(value[8..12].try_into().unwrap()), } } } impl LocalFileHeader { pub async fn from_reader(reader: &mut R) -> Result { let mut buffer: [u8; 26] = [0; 26]; reader.read_exact(&mut buffer).await?; Ok(LocalFileHeader::from(buffer)) } } impl EndOfCentralDirectoryHeader { pub async fn from_reader(reader: &mut R) -> Result { let mut buffer: [u8; 18] = [0; 18]; reader.read_exact(&mut buffer).await?; Ok(EndOfCentralDirectoryHeader::from(buffer)) } } impl CentralDirectoryRecord { pub async fn from_reader(reader: &mut R) -> Result { let mut buffer: [u8; 42] = [0; 42]; reader.read_exact(&mut buffer).await?; Ok(CentralDirectoryRecord::from(buffer)) } } impl Zip64EndOfCentralDirectoryRecord { pub async fn from_reader(reader: &mut R) -> Result { let mut buffer: [u8; 52] = [0; 52]; reader.read_exact(&mut buffer).await?; Ok(Self::from(buffer)) } pub fn as_bytes(&self) -> [u8; 52] { let mut array = [0; 52]; let mut cursor = 0; array_push!(array, cursor, self.size_of_zip64_end_of_cd_record.to_le_bytes()); array_push!(array, cursor, self.version_made_by.to_le_bytes()); array_push!(array, cursor, self.version_needed_to_extract.to_le_bytes()); array_push!(array, cursor, self.disk_number.to_le_bytes()); array_push!(array, cursor, self.disk_number_start_of_cd.to_le_bytes()); array_push!(array, cursor, self.num_entries_in_directory_on_disk.to_le_bytes()); array_push!(array, cursor, self.num_entries_in_directory.to_le_bytes()); array_push!(array, cursor, self.directory_size.to_le_bytes()); array_push!(array, cursor, self.offset_of_start_of_directory.to_le_bytes()); array } } impl DataDescriptor { pub async fn from_reader(reader: &mut R) -> Result { let mut descriptor: [u8; DATA_DESCRIPTOR_LENGTH] = [0; DATA_DESCRIPTOR_LENGTH]; reader.read_exact(&mut descriptor).await?; // The data descriptor signature is optional. if descriptor[0..SIGNATURE_LENGTH] == DATA_DESCRIPTOR_SIGNATURE.to_le_bytes() { // If present, read the remaining bytes. let mut tail: [u8; SIGNATURE_LENGTH] = [0; SIGNATURE_LENGTH]; reader.read_exact(&mut tail).await?; Ok(DataDescriptor { crc: u32::from_le_bytes(descriptor[4..8].try_into().unwrap()), compressed_size: u32::from_le_bytes(descriptor[8..12].try_into().unwrap()), uncompressed_size: u32::from_le_bytes(tail[0..4].try_into().unwrap()), }) } else { // If absent, then the first four bytes are not the signature, but instead part of the // data descriptor. Ok(DataDescriptor { crc: u32::from_le_bytes(descriptor[0..4].try_into().unwrap()), compressed_size: u32::from_le_bytes(descriptor[4..8].try_into().unwrap()), uncompressed_size: u32::from_le_bytes(descriptor[8..12].try_into().unwrap()), }) } } pub fn as_bytes(&self) -> [u8; DATA_DESCRIPTOR_LENGTH] { let mut array = [0; DATA_DESCRIPTOR_LENGTH]; let mut cursor = 0; array_push!(array, cursor, self.crc.to_le_bytes()); array_push!(array, cursor, self.compressed_size.to_le_bytes()); array_push!(array, cursor, self.uncompressed_size.to_le_bytes()); array } } impl Zip64DataDescriptor { pub async fn from_reader(reader: &mut R) -> Result { // Read the first four bytes to check for the data descriptor signature. let mut signature: [u8; SIGNATURE_LENGTH] = [0; SIGNATURE_LENGTH]; reader.read_exact(&mut signature).await?; // The data descriptor signature is optional. if signature[0..SIGNATURE_LENGTH] == DATA_DESCRIPTOR_SIGNATURE.to_le_bytes() { // If present, read the remaining bytes. let mut descriptor: [u8; ZIP64_DATA_DESCRIPTOR_LENGTH] = [0; ZIP64_DATA_DESCRIPTOR_LENGTH]; reader.read_exact(&mut descriptor).await?; Ok(Zip64DataDescriptor { crc: u32::from_le_bytes(descriptor[0..4].try_into().unwrap()), compressed_size: u64::from_le_bytes(descriptor[4..12].try_into().unwrap()), uncompressed_size: u64::from_le_bytes(descriptor[12..20].try_into().unwrap()), }) } else { // If absent, read the remaining bytes without the signature, and use the first four // bytes as the CRC. let mut descriptor: [u8; ZIP64_DATA_DESCRIPTOR_LENGTH - SIGNATURE_LENGTH] = [0; ZIP64_DATA_DESCRIPTOR_LENGTH - SIGNATURE_LENGTH]; reader.read_exact(&mut descriptor).await?; Ok(Zip64DataDescriptor { crc: u32::from_le_bytes(signature), compressed_size: u64::from_le_bytes(descriptor[0..8].try_into().unwrap()), uncompressed_size: u64::from_le_bytes(descriptor[8..16].try_into().unwrap()), }) } } pub fn as_bytes(&self) -> [u8; ZIP64_DATA_DESCRIPTOR_LENGTH] { let mut array = [0; ZIP64_DATA_DESCRIPTOR_LENGTH]; let mut cursor = 0; array_push!(array, cursor, self.crc.to_le_bytes()); array_push!(array, cursor, self.compressed_size.to_le_bytes()); array_push!(array, cursor, self.uncompressed_size.to_le_bytes()); array } } impl Zip64EndOfCentralDirectoryLocator { /// Read 4 bytes from the reader and check whether its signature matches that of the EOCDL. /// If it does, return Some(EOCDL), otherwise return None. pub async fn try_from_reader( reader: &mut R, ) -> Result> { let signature = { let mut buffer = [0; 4]; reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; if signature != ZIP64_EOCDL_SIGNATURE { return Ok(None); } let mut buffer: [u8; 16] = [0; 16]; reader.read_exact(&mut buffer).await?; Ok(Some(Self::from(buffer))) } } /// Parse the extra fields. pub fn parse_extra_fields( data: Vec, uncompressed_size: u32, compressed_size: u32, relative_header_offset: Option, disk_start_number: Option, ) -> Result> { let mut cursor = 0; let mut extra_fields = Vec::::new(); while cursor + 4 <= data.len() { let header_id: HeaderId = u16::from_le_bytes(data[cursor..cursor + 2].try_into().unwrap()).into(); let field_size = u16::from_le_bytes(data[cursor + 2..cursor + 4].try_into().unwrap()); if cursor + 4 + field_size as usize > data.len() { return Err(ZipError::InvalidExtraFieldHeader(field_size)); } // Decode the extra field data. let data = &data[cursor + 4..cursor + 4 + field_size as usize]; let extra_field = extra_field_from_bytes( header_id, field_size, data, uncompressed_size, compressed_size, relative_header_offset, disk_start_number, )?; // Verify that the extra field doesn't contain duplicates. for seen in &extra_fields { if extra_field.header_id() == seen.header_id() { return Err(ZipError::DuplicateExtraFieldHeader(header_id.into())); } } extra_fields.push(extra_field); cursor += 4 + field_size as usize; } Ok(extra_fields) } /// Replace elements of an array at a given cursor index for use with a zero-initialised array. macro_rules! array_push { ($arr:ident, $cursor:ident, $value:expr) => {{ for entry in $value { $arr[$cursor] = entry; $cursor += 1; } }}; } use crate::spec::consts::{ DATA_DESCRIPTOR_LENGTH, DATA_DESCRIPTOR_SIGNATURE, SIGNATURE_LENGTH, ZIP64_DATA_DESCRIPTOR_LENGTH, ZIP64_EOCDL_SIGNATURE, }; use crate::spec::data_descriptor::{DataDescriptor, Zip64DataDescriptor}; use crate::spec::extra_field::extra_field_from_bytes; pub(crate) use array_push; #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_zip64_eocdr() { let eocdr: [u8; 56] = [ 0x50, 0x4B, 0x06, 0x06, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x03, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let without_signature: [u8; 52] = eocdr[4..56].try_into().unwrap(); let zip64eocdr = Zip64EndOfCentralDirectoryRecord::from(without_signature); assert_eq!( zip64eocdr, Zip64EndOfCentralDirectoryRecord { size_of_zip64_end_of_cd_record: 44, version_made_by: 798, version_needed_to_extract: 45, disk_number: 0, disk_number_start_of_cd: 0, num_entries_in_directory_on_disk: 1, num_entries_in_directory: 1, directory_size: 47, offset_of_start_of_directory: 64, } ) } #[tokio::test] async fn test_parse_zip64_eocdl() { let eocdl: [u8; 20] = [ 0x50, 0x4B, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, ]; let mut cursor = futures_lite::io::Cursor::new(eocdl); let zip64eocdl = Zip64EndOfCentralDirectoryLocator::try_from_reader(&mut cursor).await.unwrap().unwrap(); assert_eq!( zip64eocdl, Zip64EndOfCentralDirectoryLocator { number_of_disk_with_start_of_zip64_end_of_central_directory: 0, relative_offset: 111, total_number_of_disks: 1, } ) } } astral_async_zip-0.0.17/src/string.rs000064400000000000000000000075321046102023000157300ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::error::{Result, ZipError}; /// A string encoding supported by this crate. #[derive(Debug, Clone, Copy)] pub enum StringEncoding { Utf8, Raw, } /// A string wrapper for handling different encodings. #[derive(Debug, Clone)] pub struct ZipString { encoding: StringEncoding, raw: Vec, alternative: Option>, } impl ZipString { /// Constructs a new encoded string from its raw bytes and its encoding type. /// /// # Note /// If the provided encoding is [`StringEncoding::Utf8`] but the raw bytes are not valid UTF-8 (ie. a call to /// `std::str::from_utf8()` fails), the encoding is defaulted back to [`StringEncoding::Raw`]. pub fn new(raw: Vec, mut encoding: StringEncoding) -> Self { if let StringEncoding::Utf8 = encoding { if std::str::from_utf8(&raw).is_err() { encoding = StringEncoding::Raw; } } Self { encoding, raw, alternative: None } } /// Constructs a new encoded string from utf-8 data, with an alternative in native MBCS encoding. pub fn new_with_alternative(utf8: String, alternative: Vec) -> Self { Self { encoding: StringEncoding::Utf8, raw: utf8.into_bytes(), alternative: Some(alternative) } } /// Returns the raw bytes for this string. pub fn as_bytes(&self) -> &[u8] { &self.raw } /// Returns the encoding type for this string. pub fn encoding(&self) -> StringEncoding { self.encoding } /// Returns the alternative bytes (in native MBCS encoding) for this string. pub fn alternative(&self) -> Option<&[u8]> { self.alternative.as_deref() } /// Returns the raw bytes converted into a string slice. /// /// # Note /// A call to this method will only succeed if the encoding type is [`StringEncoding::Utf8`]. pub fn as_str(&self) -> Result<&str> { if !matches!(self.encoding, StringEncoding::Utf8) { return Err(ZipError::StringNotUtf8); } // SAFETY: // "The bytes passed in must be valid UTF-8.' // // This function will error if self.encoding is not StringEncoding::Utf8. // // self.encoding is only ever StringEncoding::Utf8 if this variant was provided to the constructor AND the // call to `std::str::from_utf8()` within the constructor succeeded. Mutable access to the inner vector is // never given and no method implemented on this type mutates the inner vector. Ok(unsafe { std::str::from_utf8_unchecked(&self.raw) }) } /// Returns the raw bytes converted to an owned string. /// /// # Note /// A call to this method will only succeed if the encoding type is [`StringEncoding::Utf8`]. pub fn into_string(self) -> Result { if !matches!(self.encoding, StringEncoding::Utf8) { return Err(ZipError::StringNotUtf8); } // SAFETY: See above. Ok(unsafe { String::from_utf8_unchecked(self.raw) }) } /// Returns the alternative bytes (in native MBCS encoding) converted to the owned. pub fn into_alternative(self) -> Option> { self.alternative } /// Returns whether this string is encoded as utf-8 without an alternative. pub fn is_utf8_without_alternative(&self) -> bool { matches!(self.encoding, StringEncoding::Utf8) && self.alternative.is_none() } } impl From for ZipString { fn from(value: String) -> Self { Self { encoding: StringEncoding::Utf8, raw: value.into_bytes(), alternative: None } } } impl From<&str> for ZipString { fn from(value: &str) -> Self { Self { encoding: StringEncoding::Utf8, raw: value.as_bytes().to_vec(), alternative: None } } } astral_async_zip-0.0.17/src/tests/combined/mod.rs000064400000000000000000000002041046102023000201100ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) astral_async_zip-0.0.17/src/tests/mod.rs000064400000000000000000000007121046102023000163340ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod combined; pub(crate) mod read; use std::sync::Once; static ENV_LOGGER: Once = Once::new(); /// Initialize the env logger for any tests that require it. /// Safe to call multiple times. fn init_logger() { ENV_LOGGER.call_once(|| env_logger::Builder::from_default_env().format_module_path(true).init()); } astral_async_zip-0.0.17/src/tests/read/cd/mod.rs000064400000000000000000000023271046102023000176410ustar 00000000000000// Copyright (c) 2025 Astral // MIT License (https://github.com/astral-sh/rs-async-zip/blob/main/LICENSE) #[cfg(feature = "deflate")] #[tokio::test] async fn test_nonempty_cd_comment() { use futures_lite::io::Cursor; use crate::base::read::cd::{CentralDirectoryReader, Entry}; use crate::base::read::stream::ZipFileReader; use crate::tests::init_logger; init_logger(); let data = include_bytes!("nonempty_cd_comment.zip").to_vec(); let mut cursor = Cursor::new(data); let mut zip = ZipFileReader::new(&mut cursor); // Move forward through the ZIP's local file entries to reach the CD. // We do this instead of using the EOCDR locator to mimic a streaming read. let mut offset = 0; while let Some(entry) = zip.next_with_entry().await.unwrap() { (.., zip) = entry.skip().await.unwrap(); offset = zip.offset(); } let mut cdr = CentralDirectoryReader::new(&mut cursor, offset); let Entry::CentralDirectoryEntry(_) = cdr.next().await.unwrap() else { panic!("expected a central directory entry"); }; // Our position matches the end of the CD entry, including its // non-empty comment field. assert_eq!(cursor.position(), 0x2c + 52); } astral_async_zip-0.0.17/src/tests/read/cd/nonempty_cd_comment.zip000064400000000000000000000001661046102023000233000ustar 00000000000000PK!7 pocst`TPK!7 pocpocPK4,astral_async_zip-0.0.17/src/tests/read/compression/bzip2.data000064400000000000000000000020551046102023000223460ustar 00000000000000BZh61AY&SY3n@1 "h0  "(Hr7astral_async_zip-0.0.17/src/tests/read/compression/deflate.data000064400000000000000000000020111046102023000227140ustar 00000000000000KWHJ,astral_async_zip-0.0.17/src/tests/read/compression/lzma.data000064400000000000000000000020361046102023000222620ustar 00000000000000]3@b1 %/툀astral_async_zip-0.0.17/src/tests/read/compression/mod.rs000064400000000000000000000031711046102023000216120ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::read::io::compressed::CompressedReader; use crate::spec::Compression; compressed_test_helper!(stored_test, Compression::Stored, "foo bar", "foo bar"); #[cfg(feature = "deflate")] compressed_test_helper!(deflate_test, Compression::Deflate, "foo bar", include_bytes!("deflate.data")); #[cfg(feature = "bzip2")] compressed_test_helper!(bz_test, Compression::Bz, "foo bar", include_bytes!("bzip2.data")); #[cfg(feature = "lzma")] compressed_test_helper!(lzma_test, Compression::Lzma, "foo bar", include_bytes!("lzma.data")); #[cfg(feature = "zstd")] compressed_test_helper!(zstd_test, Compression::Zstd, "foo bar", include_bytes!("zstd.data")); #[cfg(feature = "xz")] compressed_test_helper!(xz_test, Compression::Xz, "foo bar", include_bytes!("xz.data")); /// A helper macro for generating a CompressedReader test using a specific compression method. macro_rules! compressed_test_helper { ($name:ident, $typ:expr, $data_raw:expr, $data:expr) => { #[cfg(test)] #[tokio::test] async fn $name() { use futures_lite::io::{AsyncReadExt, Cursor}; let data = $data; let data_raw = $data_raw; let cursor = Cursor::new(data); let mut reader = CompressedReader::new(cursor, $typ); let mut read_data = String::new(); reader.read_to_string(&mut read_data).await.expect("read into CompressedReader failed"); assert_eq!(read_data, data_raw); } }; } use compressed_test_helper; astral_async_zip-0.0.17/src/tests/read/compression/xz.data000064400000000000000000000021001046102023000217500ustar 000000000000007zXZִF!t/foo bar"P .s}YZastral_async_zip-0.0.17/src/tests/read/compression/zstd.data000064400000000000000000000020201046102023000222740ustar 00000000000000(/X9foo barastral_async_zip-0.0.17/src/tests/read/locator/empty-buffer-boundary.zip000064400000000000000000000040241046102023000245370ustar 00000000000000PKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAastral_async_zip-0.0.17/src/tests/read/locator/empty-with-max-comment.zip000064400000000000000000002000251046102023000246420ustar 00000000000000PKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAastral_async_zip-0.0.17/src/tests/read/locator/empty.zip000064400000000000000000000000261046102023000214450ustar 00000000000000PKastral_async_zip-0.0.17/src/tests/read/locator/mod.rs000064400000000000000000000036001046102023000207110ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) #[test] fn search_one_byte_test() { let buffer: &[u8] = &[0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; let signature: &[u8] = &[0x1]; let matched = crate::base::read::io::locator::reverse_search_buffer(buffer, signature); assert!(matched.is_none()); let buffer: &[u8] = &[0x2, 0x1, 0x0, 0x0, 0x0, 0x0]; let signature: &[u8] = &[0x1]; let matched = crate::base::read::io::locator::reverse_search_buffer(buffer, signature); assert!(matched.is_some()); assert_eq!(1, matched.unwrap()); } #[test] fn search_two_byte_test() { let buffer: &[u8] = &[0x2, 0x1, 0x0, 0x0, 0x0, 0x0]; let signature: &[u8] = &[0x2, 0x1]; let matched = crate::base::read::io::locator::reverse_search_buffer(buffer, signature); assert!(matched.is_some()); assert_eq!(1, matched.unwrap()); } #[tokio::test] async fn locator_empty_test() { use futures_lite::io::Cursor; let data = &include_bytes!("empty.zip"); let mut cursor = Cursor::new(data); let eocdr = crate::base::read::io::locator::eocdr(&mut cursor).await; assert!(eocdr.is_ok()); assert_eq!(eocdr.unwrap(), 4); } #[tokio::test] async fn locator_empty_max_comment_test() { use futures_lite::io::Cursor; let data = &include_bytes!("empty-with-max-comment.zip"); let mut cursor = Cursor::new(data); let eocdr = crate::base::read::io::locator::eocdr(&mut cursor).await; assert!(eocdr.is_ok()); assert_eq!(eocdr.unwrap(), 4); } #[tokio::test] async fn locator_buffer_boundary_test() { use futures_lite::io::Cursor; let data = &include_bytes!("empty-buffer-boundary.zip"); let mut cursor = Cursor::new(data); let eocdr = crate::base::read::io::locator::eocdr(&mut cursor).await; assert!(eocdr.is_ok()); assert_eq!(eocdr.unwrap(), 4); } astral_async_zip-0.0.17/src/tests/read/mod.rs000064400000000000000000000003421046102023000172460ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod cd; pub(crate) mod compression; pub(crate) mod locator; pub(crate) mod zip64; astral_async_zip-0.0.17/src/tests/read/zip64/mod.rs000064400000000000000000000067631046102023000202370ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // Copyright (c) 2023 Cognite AS // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use futures_lite::io::AsyncReadExt; use crate::tests::init_logger; const ZIP64_ZIP_CONTENTS: &str = "Hello World!\n"; /// Tests opening and reading a zip64 archive. /// It contains one file named "-" with a zip 64 extended field header. #[tokio::test] async fn test_read_zip64_archive_mem() { use crate::base::read::mem::ZipFileReader; init_logger(); let data = include_bytes!("zip64.zip").to_vec(); let reader = ZipFileReader::new(data).await.unwrap(); let mut entry_reader = reader.reader_without_entry(0).await.unwrap(); let mut read_data = String::new(); entry_reader.read_to_string(&mut read_data).await.expect("read failed"); assert_eq!( read_data.chars().count(), ZIP64_ZIP_CONTENTS.chars().count(), "{read_data:?} != {ZIP64_ZIP_CONTENTS:?}" ); assert_eq!(read_data, ZIP64_ZIP_CONTENTS); } /// Like test_read_zip64_archive_mem() but for the streaming version #[tokio::test] async fn test_read_zip64_archive_stream() { use crate::base::read::stream::ZipFileReader; init_logger(); let data = include_bytes!("zip64.zip").to_vec(); let reader = ZipFileReader::new(data.as_slice()); let mut entry_reader = reader.next_without_entry().await.unwrap().unwrap(); let mut read_data = String::new(); entry_reader.reader_mut().read_to_string(&mut read_data).await.expect("read failed"); assert_eq!( read_data.chars().count(), ZIP64_ZIP_CONTENTS.chars().count(), "{read_data:?} != {ZIP64_ZIP_CONTENTS:?}" ); assert_eq!(read_data, ZIP64_ZIP_CONTENTS); } /// Generate an example file only if it doesn't exist already. /// The file is placed adjacent to this rs file. #[cfg(feature = "tokio")] fn generate_zip64many_zip() -> std::path::PathBuf { use std::io::Write; use zip::write::{ExtendedFileOptions, FileOptions}; let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("src/tests/read/zip64/zip64many.zip"); // Only recreate the zip if it doesnt already exist. if path.exists() { return path; } let zip_file = std::fs::File::create(&path).unwrap(); let mut zip = zip::ZipWriter::new(zip_file); let options: FileOptions<'_, ExtendedFileOptions> = FileOptions::default().compression_method(zip::CompressionMethod::Stored); for i in 0..2_u32.pow(16) + 1 { zip.start_file(format!("{i}.txt"), options.clone()).unwrap(); zip.write_all(b"\n").unwrap(); } zip.finish().unwrap(); path } /// Test reading a generated zip64 archive that contains more than 2^16 entries. #[cfg(feature = "tokio-fs")] #[tokio::test] async fn test_read_zip64_archive_many_entries() { use crate::tokio::read::fs::ZipFileReader; init_logger(); let path = generate_zip64many_zip(); let reader = ZipFileReader::new(path).await.unwrap(); // Verify that each entry exists and is has the contents "\n" for i in 0..2_u32.pow(16) + 1 { let entry = reader.file().entries().get(i as usize).unwrap(); eprintln!("{:?}", entry.filename().as_bytes()); assert_eq!(entry.filename.as_str().unwrap(), format!("{i}.txt")); let mut entry = reader.reader_without_entry(i as usize).await.unwrap(); let mut contents = String::new(); entry.read_to_string(&mut contents).await.unwrap(); assert_eq!(contents, "\n"); } } astral_async_zip-0.0.17/src/tests/read/zip64/zip64.zip000064400000000000000000000003211046102023000205720ustar 00000000000000PK-m+V}- Hello World! PK-m+V} -PK,-/@PKoPK/@astral_async_zip-0.0.17/src/tokio/mod.rs000064400000000000000000000023621046102023000163220ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A set of [`tokio`]-specific type aliases and features. //! //! # Usage //! With the `tokio` feature enabled, types from the [`base`] implementation will implement additional constructors //! for use with [`tokio`]. These constructors internally implement conversion between the required async IO traits. //! They are defined as: //! - [`base::read::seek::ZipFileReader::with_tokio()`] //! - [`base::read::stream::ZipFileReader::with_tokio()`] //! - [`base::write::ZipFileWriter::with_tokio()`] //! //! As a result of Rust's type inference, we are able to reuse the [`base`] implementation's types with considerable //! ease. There only exists one caveat with their use; the types returned by these constructors contain a wrapping //! compatibility type provided by an external crate. These compatibility types cannot be named unless you also pull in //! the [`tokio_util`] dependency manually. This is why we've provided type aliases within this module so that they can //! be named without needing to pull in a separate dependency. #[cfg(doc)] use crate::base; #[cfg(doc)] use tokio; #[cfg(doc)] use tokio_util; pub mod read; astral_async_zip-0.0.17/src/tokio/read/fs.rs000064400000000000000000000123451046102023000170700ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A concurrent ZIP reader which acts over a file system path. //! //! Concurrency is achieved as a result of: //! - Wrapping the provided path within an [`Arc`] to allow shared ownership. //! - Constructing a new [`File`] from the path when reading. //! //! ### Usage //! Unlike the [`seek`] module, we no longer hold a mutable reference to any inner reader which in turn, allows the //! construction of concurrent [`ZipEntryReader`]s. Though, note that each individual [`ZipEntryReader`] cannot be sent //! between thread boundaries due to the masked lifetime requirement. Therefore, the overarching [`ZipFileReader`] //! should be cloned and moved into those contexts when needed. //! //! ### Concurrent Example //! ```no_run //! # use async_zip::tokio::read::fs::ZipFileReader; //! # use async_zip::error::Result; //! # use futures_lite::io::AsyncReadExt; //! # //! async fn run() -> Result<()> { //! let reader = ZipFileReader::new("./foo.zip").await?; //! let result = tokio::join!(read(&reader, 0), read(&reader, 1)); //! //! let data_0 = result.0?; //! let data_1 = result.1?; //! //! // Use data within current scope. //! //! Ok(()) //! } //! //! async fn read(reader: &ZipFileReader, index: usize) -> Result> { //! let mut entry = reader.reader_without_entry(index).await?; //! let mut data = Vec::new(); //! entry.read_to_end(&mut data).await?; //! Ok(data) //! } //! ``` //! //! ### Parallel Example //! ```no_run //! # use async_zip::tokio::read::fs::ZipFileReader; //! # use async_zip::error::Result; //! # use futures_lite::io::AsyncReadExt; //! # //! async fn run() -> Result<()> { //! let reader = ZipFileReader::new("./foo.zip").await?; //! //! let handle_0 = tokio::spawn(read(reader.clone(), 0)); //! let handle_1 = tokio::spawn(read(reader.clone(), 1)); //! //! let data_0 = handle_0.await.expect("thread panicked")?; //! let data_1 = handle_1.await.expect("thread panicked")?; //! //! // Use data within current scope. //! //! Ok(()) //! } //! //! async fn read(reader: ZipFileReader, index: usize) -> Result> { //! let mut entry = reader.reader_without_entry(index).await?; //! let mut data = Vec::new(); //! entry.read_to_end(&mut data).await?; //! Ok(data) //! } //! ``` #[cfg(doc)] use crate::base::read::seek; use crate::base::read::io::entry::{WithEntry, WithoutEntry, ZipEntryReader}; use crate::error::{Result, ZipError}; use crate::file::ZipFile; use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::fs::File; use tokio::io::BufReader; use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; struct Inner { path: PathBuf, file: ZipFile, } /// A concurrent ZIP reader which acts over a file system path. #[derive(Clone)] pub struct ZipFileReader { inner: Arc, } impl ZipFileReader { /// Constructs a new ZIP reader from a file system path. pub async fn new

(path: P) -> Result where P: AsRef, { let file = crate::base::read::file(File::open(&path).await?.compat()).await?; Ok(ZipFileReader::from_raw_parts(path, file)) } /// Constructs a ZIP reader from a file system path and ZIP file information derived from that path. /// /// Providing a [`ZipFile`] that wasn't derived from that path may lead to inaccurate parsing. pub fn from_raw_parts

(path: P, file: ZipFile) -> ZipFileReader where P: AsRef, { ZipFileReader { inner: Arc::new(Inner { path: path.as_ref().to_owned(), file }) } } /// Returns this ZIP file's information. pub fn file(&self) -> &ZipFile { &self.inner.file } /// Returns the file system path provided to the reader during construction. pub fn path(&self) -> &Path { &self.inner.path } /// Returns a new entry reader if the provided index is valid. pub async fn reader_without_entry( &self, index: usize, ) -> Result>, WithoutEntry>> { let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; let mut fs_file = BufReader::new(File::open(&self.inner.path).await?).compat(); stored_entry.seek_to_data_offset(&mut fs_file).await?; Ok(ZipEntryReader::new_with_owned( fs_file, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), )) } /// Returns a new entry reader if the provided index is valid. pub async fn reader_with_entry( &self, index: usize, ) -> Result>, WithEntry<'_>>> { let stored_entry = self.inner.file.entries.get(index).ok_or(ZipError::EntryIndexOutOfBounds)?; let mut fs_file = BufReader::new(File::open(&self.inner.path).await?).compat(); stored_entry.seek_to_data_offset(&mut fs_file).await?; let reader = ZipEntryReader::new_with_owned( fs_file, stored_entry.entry.compression(), stored_entry.entry.compressed_size(), ); Ok(reader.into_with_entry(stored_entry)) } } astral_async_zip-0.0.17/src/tokio/read/mod.rs000064400000000000000000000024331046102023000172340ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A module which supports reading ZIP files. use tokio_util::compat::Compat; #[cfg(feature = "tokio-fs")] pub mod fs; #[cfg(doc)] use crate::base; #[cfg(doc)] use tokio; /// A [`tokio`]-specific type alias for [`base::read::ZipEntryReader`]; pub type ZipEntryReader<'a, R, E> = crate::base::read::ZipEntryReader<'a, Compat, E>; pub mod seek { //! A ZIP reader which acts over a seekable source. use tokio_util::compat::Compat; #[cfg(doc)] use crate::base; #[cfg(doc)] use tokio; /// A [`tokio`]-specific type alias for [`base::read::seek::ZipFileReader`]; pub type ZipFileReader = crate::base::read::seek::ZipFileReader>; } pub mod stream { //! A ZIP reader which acts over a non-seekable source. #[cfg(doc)] use crate::base; #[cfg(doc)] use tokio; use tokio_util::compat::Compat; /// A [`tokio`]-specific type alias for [`base::read::stream::Reading`]; pub type Reading<'a, R, E> = crate::base::read::stream::Reading<'a, Compat, E>; /// A [`tokio`]-specific type alias for [`base::read::stream::Ready`]; pub type Ready = crate::base::read::stream::Ready>; } astral_async_zip-0.0.17/src/utils.rs000064400000000000000000000013421046102023000155530ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::error::{Result, ZipError}; use futures_lite::io::{AsyncRead, AsyncReadExt}; // Assert that the next four-byte signature read by a reader which impls AsyncRead matches the expected signature. pub(crate) async fn assert_signature(reader: &mut R, expected: u32) -> Result<()> { let signature = { let mut buffer = [0; 4]; reader.read_exact(&mut buffer).await?; u32::from_le_bytes(buffer) }; match signature { actual if actual == expected => Ok(()), actual => Err(ZipError::UnexpectedHeaderError(actual, expected)), } } astral_async_zip-0.0.17/tests/common/mod.rs000064400000000000000000000060011046102023000170320ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use async_zip::base::read::mem; use async_zip::base::read::seek; use tokio::fs::File; use tokio::io::BufReader; use tokio_util::compat::TokioAsyncReadCompatExt; const FOLDER_PREFIX: &str = "tests/test_inputs"; #[cfg(feature = "tokio-fs")] pub async fn check_decompress_fs(fname: &str) { use async_zip::tokio::read::fs; let zip = fs::ZipFileReader::new(fname).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); for (idx, entry) in zip_entries.into_iter().enumerate() { // TODO: resolve unwrap usage if entry.dir().unwrap() { continue; } // TODO: resolve unwrap usage let fname = entry.filename().as_str().unwrap(); let mut output = String::new(); let mut reader = zip.reader_with_entry(idx).await.unwrap(); let _ = reader.read_to_string_checked(&mut output).await.unwrap(); let fs_file = format!("{FOLDER_PREFIX}/{fname}"); let expected = tokio::fs::read_to_string(fs_file).await.unwrap(); assert_eq!(output, expected, "for {fname}, expect zip data to match file data"); } } pub async fn check_decompress_seek(fname: &str) { let file = BufReader::new(File::open(fname).await.unwrap()); let mut file_compat = file.compat(); let mut zip = seek::ZipFileReader::new(&mut file_compat).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); for (idx, entry) in zip_entries.into_iter().enumerate() { // TODO: resolve unwrap usage if entry.dir().unwrap() { continue; } // TODO: resolve unwrap usage let fname = entry.filename().as_str().unwrap(); let mut output = String::new(); let mut reader = zip.reader_with_entry(idx).await.unwrap(); let _ = reader.read_to_string_checked(&mut output).await.unwrap(); let fs_file = format!("tests/test_inputs/{fname}"); let expected = tokio::fs::read_to_string(fs_file).await.unwrap(); assert_eq!(output, expected, "for {fname}, expect zip data to match file data"); } } pub async fn check_decompress_mem(zip_data: Vec) { let zip = mem::ZipFileReader::new(zip_data).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); for (idx, entry) in zip_entries.into_iter().enumerate() { // TODO: resolve unwrap usage if entry.dir().unwrap() { continue; } // TODO: resolve unwrap usage let fname = entry.filename().as_str().unwrap(); let mut output = String::new(); let mut reader = zip.reader_with_entry(idx).await.unwrap(); let _ = reader.read_to_string_checked(&mut output).await.unwrap(); let fs_file = format!("{FOLDER_PREFIX}/{fname}"); let expected = tokio::fs::read_to_string(fs_file).await.unwrap(); assert_eq!(output, expected, "for {fname}, expect zip data to match file data"); } } astral_async_zip-0.0.17/tests/decompress_test.rs000064400000000000000000000050771046102023000202020ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use tokio::io::BufReader; use tokio_util::compat::TokioAsyncReadCompatExt; mod common; #[cfg(feature = "zstd")] const ZSTD_ZIP_FILE: &str = "tests/test_inputs/sample_data.zstd.zip"; #[cfg(feature = "deflate")] const DEFLATE_ZIP_FILE: &str = "tests/test_inputs/sample_data.deflate.zip"; const STORE_ZIP_FILE: &str = "tests/test_inputs/sample_data.store.zip"; const UTF8_EXTRA_ZIP_FILE: &str = "tests/test_inputs/sample_data_utf8_extra.zip"; #[cfg(feature = "zstd")] #[tokio::test] async fn decompress_zstd_zip_seek() { common::check_decompress_seek(ZSTD_ZIP_FILE).await } #[cfg(feature = "deflate")] #[tokio::test] async fn decompress_deflate_zip_seek() { common::check_decompress_seek(DEFLATE_ZIP_FILE).await } #[tokio::test] async fn decompress_store_zip_seek() { common::check_decompress_seek(STORE_ZIP_FILE).await } #[cfg(feature = "zstd")] #[tokio::test] async fn decompress_zstd_zip_mem() { let content = tokio::fs::read(ZSTD_ZIP_FILE).await.unwrap(); common::check_decompress_mem(content).await } #[cfg(feature = "deflate")] #[tokio::test] async fn decompress_deflate_zip_mem() { let content = tokio::fs::read(DEFLATE_ZIP_FILE).await.unwrap(); common::check_decompress_mem(content).await } #[tokio::test] async fn decompress_store_zip_mem() { let content = tokio::fs::read(STORE_ZIP_FILE).await.unwrap(); common::check_decompress_mem(content).await } #[cfg(feature = "zstd")] #[cfg(feature = "tokio-fs")] #[tokio::test] async fn decompress_zstd_zip_fs() { common::check_decompress_fs(ZSTD_ZIP_FILE).await } #[cfg(feature = "deflate")] #[cfg(feature = "tokio-fs")] #[tokio::test] async fn decompress_deflate_zip_fs() { common::check_decompress_fs(DEFLATE_ZIP_FILE).await } #[cfg(feature = "tokio-fs")] #[tokio::test] async fn decompress_store_zip_fs() { common::check_decompress_fs(STORE_ZIP_FILE).await } #[tokio::test] async fn decompress_zip_with_utf8_extra() { let file = BufReader::new(tokio::fs::File::open(UTF8_EXTRA_ZIP_FILE).await.unwrap()); let mut file_compat = file.compat(); let zip = async_zip::base::read::seek::ZipFileReader::new(&mut file_compat).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); assert_eq!(zip_entries.len(), 1); assert_eq!(zip_entries[0].header_size(), 93); assert_eq!(zip_entries[0].filename().as_str().unwrap(), "\u{4E2D}\u{6587}.txt"); assert_eq!(zip_entries[0].filename().alternative(), Some(b"\xD6\xD0\xCe\xC4.txt".as_ref())); } astral_async_zip-0.0.17/tests/test_inputs/sample_data/alpha/back_to_front.txt000064400000000000000000000006401046102023000257130ustar 00000000000000Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a astral_async_zip-0.0.17/tests/test_inputs/sample_data/alpha/front_to_back.txt000064400000000000000000000006401046102023000257130ustar 00000000000000A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z astral_async_zip-0.0.17/tests/test_inputs/sample_data/numeric/forward.txt000064400000000000000000000001271046102023000251220ustar 000000000000001,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 astral_async_zip-0.0.17/tests/test_inputs/sample_data/numeric/reverse.txt000064400000000000000000000001271046102023000251310ustar 0000000000000032,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1 astral_async_zip-0.0.17/tests/test_inputs/sample_data.deflate.zip000064400000000000000000000022661046102023000234100ustar 00000000000000PK0V sample_data/PK0Vsample_data/numeric/PK'0V (5Wsample_data/numeric/forward.txt >@ofr'.p,xb#HBlr%M0OsУBI r!,d"!n\80(.͢ PK0Vsample_data/alpha/PKl0V2>W#sample_data/alpha/front_to_back.txtd ]O2 -,%*RU+5kuWSemck(t9] \ ݌܍=LW#xsample_data/alpha/front_to_back.txtPK?=0VFmUW#sample_data/alpha/back_to_front.txtPKastral_async_zip-0.0.17/tests/test_inputs/sample_data.store.zip000064400000000000000000000036041046102023000231350ustar 00000000000000PK0V sample_data/PK0Vsample_data/numeric/PK '0V (WWsample_data/numeric/forward.txt1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 PK 10V[WWsample_data/numeric/reverse.txt32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1 PK0Vsample_data/alpha/PK l0V2>#sample_data/alpha/front_to_back.txtA,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z A,a,B,b,C,c,D,d,E,e,F,f,G,g,H,h,I,I,J,j,K,k,L,l,M,m,N,n,O,o,P,p,Q,q,R,r,S,s,T,t,U,u,V,v,W,w,X,x,Y,y,Z,z PK =0VFmU#sample_data/alpha/back_to_front.txtZ,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a Z,z,Y,y,X,x,W,w,V,v,U,u,T,t,S,s,R,r,Q,q,P,p,O,o,N,n,M,m,L,l,K,k,J,j,I,I,H,h,G,g,F,f,E,e,D,d,C,c,B,b,A,a PK?0V Asample_data/PK?0VA*sample_data/numeric/PK? '0V (WW\sample_data/numeric/forward.txtPK? 10V[WWsample_data/numeric/reverse.txtPK?0VAsample_data/alpha/PK? l0V2>#sample_data/alpha/front_to_back.txtPK? =0VFmU#sample_data/alpha/back_to_front.txtPKvastral_async_zip-0.0.17/tests/test_inputs/sample_data.zstd.zip000064400000000000000000000023551046102023000227670ustar 00000000000000PK]0V sample_data/(/ PK]0V sample_data/numeric/(/ PK]'0V (<Wsample_data/numeric/forward.txt(/Xr 7f lYcgZ hQCGRCa$ǹ PK]10V[<Wsample_data/numeric/reverse.txt(/Xr 7f2( NG-ngǒ-Ê PK]0V sample_data/alpha/(/ PK]l0V2>b#sample_data/alpha/front_to_back.txt(/X0wk8:`-@(VAEDFD@JU*$㩣ic)#⨢hb("᧡ga'!ঠf`&bv/J-ev+yhͪ(PK]=0VFmUb#sample_data/alpha/back_to_front.txt(/X0wk8:`-@(VAEDFD@JUU:Vj-\fjn rvz~"&*.26:>Bhͪ(PK?]0V Asample_data/PK?]0V A3sample_data/numeric/PK?]'0V (<Wnsample_data/numeric/forward.txtPK?]10V[<Wsample_data/numeric/reverse.txtPK?]0V A`sample_data/alpha/PK?]l0V2>b#sample_data/alpha/front_to_back.txtPK?]=0VFmUb#<sample_data/alpha/back_to_front.txtPKastral_async_zip-0.0.17/tests/test_inputs/sample_data_utf8_extra.zip000064400000000000000000000003001046102023000241410ustar 00000000000000PKr W.txtup2中文.txtPKr W7 .txt YYYup2中文.txtPKm9