async_zip-0.0.18/.cargo_vcs_info.json0000644000000001360000000000100131210ustar { "git": { "sha1": "95545ed4a60dbb7872775fac23619e61b073ad10" }, "path_in_vcs": "" }async_zip-0.0.18/.github/dependabot.yml000064400000000000000000000004361046102023000161040ustar 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" async_zip-0.0.18/.github/workflows/ci-clippy.yml000064400000000000000000000004501046102023000177210ustar 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::allasync_zip-0.0.18/.github/workflows/ci-fmt.yml000064400000000000000000000004161046102023000172110ustar 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 --checkasync_zip-0.0.18/.github/workflows/ci-linux.yml000064400000000000000000000021611046102023000175610ustar 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 async_zip-0.0.18/.github/workflows/ci-typos.yml000064400000000000000000000005141046102023000176000ustar 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 briefasync_zip-0.0.18/.github/workflows/ci-wasm.yml000064400000000000000000000007501046102023000173730ustar 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-unknown-unknown steps: - uses: actions/checkout@v4 - run: rustup target add ${{ matrix.target }} - run: cargo build --verbose --target ${{ matrix.target }} --features full-wasm async_zip-0.0.18/.gitignore000064400000000000000000000006771046102023000137130ustar 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 async_zip-0.0.18/Cargo.lock0000644000001657270000000000100111160ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "actix-codec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" dependencies = [ "bitflags", "bytes", "futures-core", "futures-sink", "log", "memchr", "pin-project-lite", "tokio", "tokio-util", ] [[package]] name = "actix-http" version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "ahash 0.8.3", "base64", "bitflags", "brotli", "bytes", "bytestring", "derive_more 0.99.17", "encoding_rs", "flate2", "futures-core", "h2", "http", "httparse", "httpdate", "itoa", "language-tags", "local-channel", "mime", "percent-encoding", "pin-project-lite", "rand", "sha1", "smallvec", "tokio", "tokio-util", "tracing", "zstd 0.12.3+zstd.1.5.2", ] [[package]] name = "actix-macros" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ "quote", "syn 1.0.102", ] [[package]] name = "actix-multipart" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53" dependencies = [ "actix-multipart-derive", "actix-utils", "actix-web", "derive_more 0.99.17", "futures-core", "futures-util", "httparse", "local-waker", "log", "memchr", "mime", "rand", "serde", "serde_json", "serde_plain", "tempfile", "tokio", ] [[package]] name = "actix-multipart-derive" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b" dependencies = [ "darling", "parse-size", "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "actix-router" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" dependencies = [ "bytestring", "http", "regex", "serde", "tracing", ] [[package]] name = "actix-rt" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" dependencies = [ "futures-core", "tokio", ] [[package]] name = "actix-server" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" dependencies = [ "actix-rt", "actix-service", "actix-utils", "futures-core", "futures-util", "mio", "num_cpus", "socket2", "tokio", "tracing", ] [[package]] name = "actix-service" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", "pin-project-lite", ] [[package]] name = "actix-utils" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", "pin-project-lite", ] [[package]] name = "actix-web" version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" dependencies = [ "actix-codec", "actix-http", "actix-macros", "actix-router", "actix-rt", "actix-server", "actix-service", "actix-utils", "actix-web-codegen", "ahash 0.7.6", "bytes", "bytestring", "cfg-if", "cookie", "derive_more 0.99.17", "encoding_rs", "futures-core", "futures-util", "http", "itoa", "language-tags", "log", "mime", "once_cell", "pin-project-lite", "regex", "serde", "serde_json", "serde_urlencoded", "smallvec", "socket2", "time", "url", ] [[package]] name = "actix-web-codegen" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" dependencies = [ "actix-router", "proc-macro2", "quote", "syn 1.0.102", ] [[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 = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] [[package]] name = "async-compression" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "bzip2 0.6.0", "deflate64", "flate2", "futures-core", "futures-io", "liblzma", "memchr", "pin-project-lite", "zstd 0.13.2", "zstd-safe 7.1.0", ] [[package]] name = "async_zip" version = "0.0.18" dependencies = [ "actix-multipart", "actix-web", "anyhow", "async-compression", "chrono", "crc32fast", "derive_more 1.0.0", "env_logger", "futures", "futures-lite", "pin-project", "sanitize-filename", "thiserror 2.0.12", "tokio", "tokio-util", "uuid", "zip", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[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 = "brotli" version = "3.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bytestring" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" dependencies = [ "bytes", ] [[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.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" dependencies = [ "libbz2-rs-sys", ] [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 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 = "chrono" version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "windows-targets 0.52.4", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" 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.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[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 = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.104", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn 2.0.104", ] [[package]] name = "deflate64" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", "syn 1.0.102", ] [[package]] name = "derive_more" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", "unicode-xid", ] [[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 = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "encoding_rs" version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] [[package]] name = "env_filter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 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 = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[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.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" dependencies = [ "fastrand 2.0.1", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", "syn 1.0.102", ] [[package]] name = "futures-sink" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[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.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "h2" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap 1.9.3", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "http" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", ] [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "generic-array", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", "windows-sys 0.48.0", ] [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] [[package]] name = "language-tags" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "libbz2-rs-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "liblzma" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8" dependencies = [ "liblzma-sys", ] [[package]] name = "liblzma-sys" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "linux-raw-sys" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" [[package]] name = "local-channel" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" dependencies = [ "futures-core", "futures-sink", "futures-util", "local-waker", ] [[package]] name = "local-waker" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" [[package]] name = "lock_api" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 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.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[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 = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi", "windows-sys 0.42.0", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi 0.1.19", "libc", ] [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", "redox_syscall 0.2.16", "smallvec", "windows-sys 0.42.0", ] [[package]] name = "parse-size" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" [[package]] name = "paste" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", ] [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", "syn 1.0.102", ] [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.37.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "sanitize-filename" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" dependencies = [ "regex", ] [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "serde_json" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_plain" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand 1.9.0", "redox_syscall 0.3.5", "rustix", "windows-sys 0.45.0", ] [[package]] name = "thiserror" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl 1.0.63", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn 2.0.104", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", "syn 1.0.102", ] [[package]] name = "tokio-util" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-xid" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "url" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ "getrandom", "serde", ] [[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 = "wasm-bindgen" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 1.0.102", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", "syn 1.0.102", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.4", ] [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[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.0", ] [[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.4", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm 0.52.4", "windows_aarch64_msvc 0.52.4", "windows_i686_gnu 0.52.4", "windows_i686_msvc 0.52.4", "windows_x86_64_gnu 0.52.4", "windows_x86_64_gnullvm 0.52.4", "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[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 2.0.104", ] [[package]] name = "zip" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ "aes", "arbitrary", "bzip2 0.4.4", "constant_time_eq", "crc32fast", "crossbeam-utils", "deflate64", "displaydoc", "flate2", "hmac", "indexmap 2.5.0", "lzma-rs", "memchr", "pbkdf2", "rand", "sha1", "thiserror 1.0.63", "time", "zeroize", "zopfli", "zstd 0.13.2", ] [[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.12.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" dependencies = [ "zstd-safe 6.0.5+zstd.1.5.4", ] [[package]] name = "zstd" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe 7.1.0", ] [[package]] name = "zstd-safe" version = "6.0.5+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" dependencies = [ "libc", "zstd-sys", ] [[package]] name = "zstd-safe" version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", ] async_zip-0.0.18/Cargo.toml0000644000000063000000000000100111160ustar # 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 = "async_zip" version = "0.0.18" 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 = [ "chrono", "tokio-fs", "deflate", "bzip2", "lzma", "zstd", "xz", "deflate64", ] full-wasm = [ "chrono", "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" [[example]] name = "actix_multipart" path = "examples/actix_multipart.rs" [[example]] name = "cli_compress" path = "examples/cli_compress.rs" [[example]] name = "file_extraction" path = "examples/file_extraction.rs" [[test]] name = "compress_test" path = "tests/compress_test.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.chrono] version = "0.4" features = ["clock"] 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 = "2" [dependencies.tokio] version = "1" optional = true default-features = false [dependencies.tokio-util] version = "0.7" features = ["compat"] optional = true [dev-dependencies.actix-multipart] version = "0.7" [dev-dependencies.actix-web] version = "4" [dev-dependencies.anyhow] version = "1" [dev-dependencies.derive_more] version = "1.0" features = [ "display", "error", ] [dev-dependencies.env_logger] version = "0.11.2" [dev-dependencies.futures] version = "0.3" [dev-dependencies.sanitize-filename] version = "0.6" [dev-dependencies.tokio] version = "1" features = ["full"] [dev-dependencies.tokio-util] version = "0.7" features = ["compat"] [dev-dependencies.uuid] version = "1" features = [ "v4", "serde", ] [dev-dependencies.zip] version = "2.1.5" async_zip-0.0.18/Cargo.toml.orig000064400000000000000000000037211046102023000146030ustar 00000000000000[package] name = "async_zip" version = "0.0.18" 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"] [features] full = ["chrono", "tokio-fs", "deflate", "bzip2", "lzma", "zstd", "xz", "deflate64"] # All features that are compatible with WASM full-wasm = ["chrono", "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 = "2" async-compression = { version = "0.4.2", default-features = false, features = ["futures-io"], optional = true } chrono = { version = "0.4", default-features = false, features = ["clock"], 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" # shared across multiple examples anyhow = "1" sanitize-filename = "0.6" # actix_multipart actix-web = "4" actix-multipart = "0.7" futures = "0.3" derive_more = { version = "1.0", features = ["display", "error"] } uuid = { version = "1", features = ["v4", "serde"] } async_zip-0.0.18/LICENSE000064400000000000000000000021041046102023000127130ustar 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. async_zip-0.0.18/README.md000064400000000000000000000072541046102023000132000ustar 00000000000000# async_zip [![Crates.io](https://img.shields.io/crates/v/async_zip?style=flat-square)](https://crates.io/crates/async_zip) [![Crates.io](https://img.shields.io/crates/d/async_zip?style=flat-square)](https://crates.io/crates/async_zip) [![docs.rs](https://img.shields.io/docsrs/async_zip?style=flat-square)](https://docs.rs/async_zip/) [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/Majored/rs-async-zip/ci-linux.yml?branch=main&style=flat-square)](https://github.com/Majored/rs-async-zip/actions?query=branch%3Amain) [![GitHub](https://img.shields.io/github/license/Majored/rs-async-zip?style=flat-square)](https://github.com/Majored/rs-async-zip/blob/main/LICENSE) 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, etc). - Support for writing complete data (u8 slices) or streams 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 & Basic Usage ```toml [dependencies] async_zip = { version = "0.0.17", features = ["full"] } ``` A (soon to be) extensive list of [examples](https://github.com/Majored/rs-async-zip/tree/main/examples) can be found under the `/examples` directory. ### 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. ### Reading ```rust use tokio::{io::BufReader, fs::File}; use async_zip::tokio::read::seek::ZipFileReader; ... let mut file = BufReader::new(File::open("./Archive.zip").await?); let mut zip = ZipFileReader::with_tokio(&mut file).await?; let mut string = String::new(); let mut reader = zip.reader_with_entry(0).await?; reader.read_to_string_checked(&mut string).await?; println!("{}", string); ``` ### Writing ```rust use async_zip::tokio::write::ZipFileWriter; use async_zip::{Compression, ZipEntryBuilder}; use tokio::fs::File; ... let mut file = File::create("foo.zip").await?; let mut writer = ZipFileWriter::with_tokio(&mut file); let data = b"This is an example file."; let builder = ZipEntryBuilder::new("bar.txt".into(), Compression::Deflate); writer.write_entry_whole(builder, data).await?; writer.close().await?; ``` ## Contributions Whilst I will be continuing to maintain this crate myself, reasonable specification compliance is a huge undertaking for a single individual. As such, contributions will always be encouraged and appreciated. No contribution guidelines exist but additions should be developed with readability in mind, with appropriate comments, and make use of `rustfmt`. ## Issues & Support Whether you're wanting to report a bug you've come across during use of this crate or are seeking general help/assistance, please utilise the [issues tracker](https://github.com/Majored/rs-async-zip/issues) and provide as much detail as possible (eg. recreation steps). I try to respond to issues within a reasonable timeframe. async_zip-0.0.18/examples/actix_multipart.rs000064400000000000000000000053051046102023000173110ustar 00000000000000// Copyright (c) 2022 FL33TW00D (https://github.com/FL33TW00D) // Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE #[cfg(features = "deflate")] mod inner { use async_zip::write::ZipFileWriter; use async_zip::{Compression, ZipEntryBuilder}; use std::path::Path; use actix_multipart::Multipart; use actix_web::{web, App, HttpServer, Responder, ResponseError, Result}; use derive_more::{Display, Error}; use futures::StreamExt; use futures_lite::io::AsyncWriteExt; use tokio::fs::File; use uuid::Uuid; const TMP_DIR: &str = "./tmp/"; #[derive(Debug, Display, Error)] #[display("An error occurred during ZIP creation which was logged to stderr.")] struct CreationError; impl ResponseError for CreationError {} async fn do_main() -> std::io::Result<()> { let tmp_path = Path::new(TMP_DIR); if !tmp_path.exists() { tokio::fs::create_dir(tmp_path).await?; } let factory = || App::new().route("/", web::post().to(handler)); HttpServer::new(factory).bind(("127.0.0.1", 8080))?.run().await } async fn handler(multipart: Multipart) -> Result { match create_archive(multipart).await { Ok(name) => Ok(format!("Successfully created archive: {}", name)), Err(err) => { eprintln!("[ERROR] {:?}", err); Err(CreationError) } } } async fn create_archive(mut body: Multipart) -> Result { let archive_name = format!("tmp/{}", Uuid::new_v4()); let mut archive = File::create(archive_name.clone()).await?; let mut writer = ZipFileWriter::new(&mut archive); while let Some(item) = body.next().await { let mut field = item?; let filename = match field.content_disposition().get_filename() { Some(filename) => sanitize_filename::sanitize(filename), None => Uuid::new_v4().to_string(), }; let builder = ZipEntryBuilder::new(filename, Compression::Deflate); let mut entry_writer = writer.write_entry_stream(builder).await.unwrap(); while let Some(chunk) = field.next().await { entry_writer.write_all_buf(&mut chunk?).await?; } entry_writer.close().await.unwrap(); } writer.close().await.unwrap(); archive.shutdown().await.unwrap(); Ok(archive_name) } } #[actix_web::main] async fn main() -> std::io::Result<()> { #[cfg(features = "deflate")] { inner::do_main().await?; } Ok(()) } async_zip-0.0.18/examples/cli_compress.rs000064400000000000000000000100631046102023000165570ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) #[tokio::main] async fn main() { #[cfg(features = "deflate")] if let Err(err) = inner::run().await { eprintln!("Error: {}", err); eprintln!("Usage: cli_compress "); std::process::exit(1); } } #[cfg(features = "deflate")] mod inner { use async_zip::base::write::ZipFileWriter; use async_zip::{Compression, ZipEntryBuilder}; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Result}; use futures_lite::io::AsyncReadExt; use tokio::fs::File; async fn run() -> Result<()> { let mut args = std::env::args().skip(1); let input_str = args.next().ok_or(anyhow!("No input file or directory specified."))?; let input_path = Path::new(&input_str); let output_str = args.next().ok_or(anyhow!("No output file specified."))?; let output_path = Path::new(&output_str); let input_pathbuf = input_path.canonicalize().map_err(|_| anyhow!("Unable to canonicalise input path."))?; let input_path = input_pathbuf.as_path(); if output_path.exists() { bail!("The output file specified already exists."); } if !input_path.exists() { bail!("The input file or directory specified doesn't exist."); } let mut output_writer = ZipFileWriter::new(File::create(output_path).await?); if input_path.is_dir() { handle_directory(input_path, &mut output_writer).await?; } else { handle_singular(input_path, &mut output_writer).await?; } output_writer.close().await?; println!("Successfully written ZIP file '{}'.", output_path.display()); Ok(()) } async fn handle_singular(input_path: &Path, writer: &mut ZipFileWriter) -> Result<()> { let filename = input_path.file_name().ok_or(anyhow!("Input path terminates in '...'."))?; let filename = filename.to_str().ok_or(anyhow!("Input path not valid UTF-8."))?; write_entry(filename, input_path, writer).await } async fn handle_directory(input_path: &Path, writer: &mut ZipFileWriter) -> Result<()> { let entries = walk_dir(input_path.into()).await?; let input_dir_str = input_path.as_os_str().to_str().ok_or(anyhow!("Input path not valid UTF-8."))?; for entry_path_buf in entries { let entry_path = entry_path_buf.as_path(); let entry_str = entry_path.as_os_str().to_str().ok_or(anyhow!("Directory file path not valid UTF-8."))?; if !entry_str.starts_with(input_dir_str) { bail!("Directory file path does not start with base input directory path."); } let entry_str = &entry_str[input_dir_str.len() + 1..]; write_entry(entry_str, entry_path, writer).await?; } Ok(()) } async fn write_entry(filename: &str, input_path: &Path, writer: &mut ZipFileWriter) -> Result<()> { let mut input_file = File::open(input_path).await?; let input_file_size = input_file.metadata().await?.len() as usize; let mut buffer = Vec::with_capacity(input_file_size); input_file.read_to_end(&mut buffer).await?; let builder = ZipEntryBuilder::new(filename.into(), Compression::Deflate); writer.write_entry_whole(builder, &buffer).await?; Ok(()) } async fn walk_dir(dir: PathBuf) -> Result> { let mut dirs = vec![dir]; let mut files = vec![]; while !dirs.is_empty() { let mut dir_iter = tokio::fs::read_dir(dirs.remove(0)).await?; while let Some(entry) = dir_iter.next_entry().await? { let entry_path_buf = entry.path(); if entry_path_buf.is_dir() { dirs.push(entry_path_buf); } else { files.push(entry_path_buf); } } } Ok(files) } } async_zip-0.0.18/examples/file_extraction.rs000064400000000000000000000070101046102023000172520ustar 00000000000000//! Demonstrates how to safely extract everything from a ZIP file. //! //! Extracting zip files from untrusted sources without proper sanitization //! could be exploited by directory traversal attacks. //! //! //! This example tries to minimize that risk by following the implementation from //! Python's Standard Library. //! //! //! use std::{ env::current_dir, path::{Path, PathBuf}, }; use async_zip::base::read::seek::ZipFileReader; use tokio::{ fs::{create_dir_all, File, OpenOptions}, io::BufReader, }; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; #[tokio::main] async fn main() { let archive = File::open("example.zip").await.expect("Failed to open zip file"); let out_dir = current_dir().expect("Failed to get current working directory"); unzip_file(archive, &out_dir).await; } /// Returns a relative path without reserved names, redundant separators, ".", or "..". fn sanitize_file_path(path: &str) -> PathBuf { // Replaces backwards slashes path.replace('\\', "/") // Sanitizes each component .split('/') .map(sanitize_filename::sanitize) .collect() } /// Extracts everything from the ZIP archive to the output directory async fn unzip_file(archive: File, out_dir: &Path) { let archive = BufReader::new(archive).compat(); let mut reader = ZipFileReader::new(archive).await.expect("Failed to read zip file"); for index in 0..reader.file().entries().len() { let entry = reader.file().entries().get(index).unwrap(); let path = out_dir.join(sanitize_file_path(entry.filename().as_str().unwrap())); // If the filename of the entry ends with '/', it is treated as a directory. // This is implemented by previous versions of this crate and the Python Standard Library. // https://docs.rs/async_zip/0.0.8/src/async_zip/read/mod.rs.html#63-65 // https://github.com/python/cpython/blob/820ef62833bd2d84a141adedd9a05998595d6b6d/Lib/zipfile.py#L528 let entry_is_dir = entry.dir().unwrap(); let mut entry_reader = reader.reader_without_entry(index).await.expect("Failed to read ZipEntry"); if entry_is_dir { // The directory may have been created if iteration is out of order. if !path.exists() { create_dir_all(&path).await.expect("Failed to create extracted directory"); } } else { // Creates parent directories. They may not exist if iteration is out of order // or the archive does not contain directory entries. let parent = path.parent().expect("A file entry should have parent directories"); if !parent.is_dir() { create_dir_all(parent).await.expect("Failed to create parent directories"); } let writer = OpenOptions::new() .write(true) .create_new(true) .open(&path) .await .expect("Failed to create extracted file"); futures_lite::io::copy(&mut entry_reader, &mut writer.compat_write()) .await .expect("Failed to copy to extracted file"); // Closes the file and manipulates its metadata here if you wish to preserve its metadata from the archive. } } } async_zip-0.0.18/rustfmt.toml000064400000000000000000000000541046102023000143110ustar 00000000000000max_width = 120 use_small_heuristics = "Max"async_zip-0.0.18/src/base/mod.rs000064400000000000000000000003521046102023000145370ustar 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; pub mod write; async_zip-0.0.18/src/base/read/io/combined_record.rs000064400000000000000000000055301046102023000204230ustar 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 } } // 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, } } } async_zip-0.0.18/src/base/read/io/compressed.rs000064400000000000000000000077301046102023000174550ustar 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 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), } } } async_zip-0.0.18/src/base/read/io/entry.rs000064400000000000000000000104521046102023000164450ustar 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, 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(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(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() } /// Consumes this reader and returns the inner value. pub(crate) fn into_inner(self) -> R { self.reader.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, } } } async_zip-0.0.18/src/base/read/io/hashed.rs000064400000000000000000000033521046102023000165410ustar 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() } /// 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)) } } async_zip-0.0.18/src/base/read/io/locator.rs000064400000000000000000000120021046102023000167400ustar 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(BUFFER_SIZE as u64); reader.seek(SeekFrom::Start(position)).await?; loop { let mut read = 0; // Read repeatedly until eof or the buffer is full. while read < BUFFER_SIZE { let bytes_read = reader.read(&mut buffer[read..]).await?; if bytes_read == 0 { break; } read += bytes_read; } 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 } async_zip-0.0.18/src/base/read/io/mod.rs000064400000000000000000000030141046102023000160570ustar 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::{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) } /// 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; async_zip-0.0.18/src/base/read/io/owned.rs000064400000000000000000000037051046102023000164230ustar 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), } } } async_zip-0.0.18/src/base/read/mem.rs000064400000000000000000000117501046102023000154550ustar 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)) } } async_zip-0.0.18/src/base/read/mod.rs000064400000000000000000000303031046102023000154510ustar 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(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, SeekFrom}; 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?; let entries = crate::base::read::cd(reader, 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 } pub(crate) fn get_zip64_extra_field_mut( extra_fields: &mut [ExtraField], ) -> Option<&mut 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 { uncompressed_size = s; } if let Some(s) = extra_field.compressed_size { 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)?; 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, }; Ok(StoredZipEntry { entry, file_offset, header_size: header_size + trailing_size }) } pub(crate) async fn lfh(mut reader: R) -> 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)?; 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, }; 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 { if basic_is_utf8 { ZipString::new(basic, StringEncoding::Utf8) } else { let unicode_extra = extra_fields.iter().find_map(|field| match field { ExtraField::InfoZipUnicodePath(InfoZipUnicodePathExtraField::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) } } } } async_zip-0.0.18/src/base/read/seek.rs000064400000000000000000000111101046102023000156140ustar 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)) } } async_zip-0.0.18/src/base/read/stream.rs000064400000000000000000000154001046102023000161660ustar 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::io::entry::ZipEntryReader; use crate::error::Result; use crate::error::ZipError; use crate::spec::consts::DATA_DESCRIPTOR_LENGTH; use crate::spec::consts::DATA_DESCRIPTOR_SIGNATURE; use crate::spec::consts::SIGNATURE_LENGTH; #[cfg(feature = "tokio")] use crate::tokio::read::stream::Ready as TokioReady; use futures_lite::io::AsyncBufRead; use futures_lite::io::AsyncReadExt; #[cfg(feature = "tokio")] use tokio_util::compat::TokioAsyncReadCompatExt; use super::io::entry::WithEntry; use super::io::entry::WithoutEntry; /// 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>, bool); /// 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(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>>> { let entry = match crate::base::read::lfh(&mut self.0 .0).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); Ok(Some(ZipFileReader(Reading(reader, entry.data_descriptor)))) } /// Opens the next entry for reading if the central directory hasn’t yet been reached. pub async fn next_with_entry(mut self) -> Result>>>> { let entry = match crate::base::read::lfh(&mut self.0 .0).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 data_descriptor = entry.data_descriptor; Ok(Some(ZipFileReader(Reading(reader.into_with_entry_owned(entry), data_descriptor)))) } /// Consumes the `ZipFileReader` returning the original `reader` pub async fn into_inner(self) -> R { self.0 .0 } } #[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())) } } 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(); // Has data descriptor. if self.0 .1 { consume_data_descriptor(&mut inner).await?; } Ok(ZipFileReader(Ready(inner))) } /// 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(); // Has data descriptor. if self.0 .1 { consume_data_descriptor(&mut inner).await?; } Ok(ZipFileReader(Ready(inner))) } } async fn consume_data_descriptor(reader: &mut R) -> Result<()> { let mut descriptor: [u8; DATA_DESCRIPTOR_LENGTH] = [0; DATA_DESCRIPTOR_LENGTH]; reader.read_exact(&mut descriptor).await?; if descriptor[0..SIGNATURE_LENGTH] == DATA_DESCRIPTOR_SIGNATURE.to_le_bytes() { let mut tail: [u8; SIGNATURE_LENGTH] = [0; SIGNATURE_LENGTH]; reader.read_exact(&mut tail).await?; } Ok(()) } async_zip-0.0.18/src/base/write/compressed_writer.rs000064400000000000000000000151401046102023000206530ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::write::io::offset::AsyncOffsetWriter; use crate::spec::Compression; use std::io::Error; use std::pin::Pin; use std::task::{Context, Poll}; #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] use async_compression::futures::write; use futures_lite::io::AsyncWrite; pub enum CompressedAsyncWriter<'b, W: AsyncWrite + Unpin> { Stored(ShutdownIgnoredWriter<&'b mut AsyncOffsetWriter>), #[cfg(feature = "deflate")] Deflate(write::DeflateEncoder>>), #[cfg(feature = "bzip2")] Bz(write::BzEncoder>>), #[cfg(feature = "lzma")] Lzma(write::LzmaEncoder>>), #[cfg(feature = "zstd")] Zstd(write::ZstdEncoder>>), #[cfg(feature = "xz")] Xz(write::XzEncoder>>), } impl<'b, W: AsyncWrite + Unpin> CompressedAsyncWriter<'b, W> { pub fn from_raw(writer: &'b mut AsyncOffsetWriter, compression: Compression, precompressed: bool) -> Self { if precompressed { return CompressedAsyncWriter::Stored(ShutdownIgnoredWriter(writer)); } match compression { Compression::Stored => CompressedAsyncWriter::Stored(ShutdownIgnoredWriter(writer)), #[cfg(feature = "deflate")] Compression::Deflate => { CompressedAsyncWriter::Deflate(write::DeflateEncoder::new(ShutdownIgnoredWriter(writer))) } #[cfg(feature = "deflate64")] Compression::Deflate64 => panic!("writing deflate64 is not supported"), #[cfg(feature = "bzip2")] Compression::Bz => CompressedAsyncWriter::Bz(write::BzEncoder::new(ShutdownIgnoredWriter(writer))), #[cfg(feature = "lzma")] Compression::Lzma => CompressedAsyncWriter::Lzma(write::LzmaEncoder::new(ShutdownIgnoredWriter(writer))), #[cfg(feature = "zstd")] Compression::Zstd => CompressedAsyncWriter::Zstd(write::ZstdEncoder::new(ShutdownIgnoredWriter(writer))), #[cfg(feature = "xz")] Compression::Xz => CompressedAsyncWriter::Xz(write::XzEncoder::new(ShutdownIgnoredWriter(writer))), } } pub fn into_inner(self) -> &'b mut AsyncOffsetWriter { match self { CompressedAsyncWriter::Stored(inner) => inner.into_inner(), #[cfg(feature = "deflate")] CompressedAsyncWriter::Deflate(inner) => inner.into_inner().into_inner(), #[cfg(feature = "bzip2")] CompressedAsyncWriter::Bz(inner) => inner.into_inner().into_inner(), #[cfg(feature = "lzma")] CompressedAsyncWriter::Lzma(inner) => inner.into_inner().into_inner(), #[cfg(feature = "zstd")] CompressedAsyncWriter::Zstd(inner) => inner.into_inner().into_inner(), #[cfg(feature = "xz")] CompressedAsyncWriter::Xz(inner) => inner.into_inner().into_inner(), } } } impl<'b, W: AsyncWrite + Unpin> AsyncWrite for CompressedAsyncWriter<'b, W> { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { match *self { CompressedAsyncWriter::Stored(ref mut inner) => Pin::new(inner).poll_write(cx, buf), #[cfg(feature = "deflate")] CompressedAsyncWriter::Deflate(ref mut inner) => Pin::new(inner).poll_write(cx, buf), #[cfg(feature = "bzip2")] CompressedAsyncWriter::Bz(ref mut inner) => Pin::new(inner).poll_write(cx, buf), #[cfg(feature = "lzma")] CompressedAsyncWriter::Lzma(ref mut inner) => Pin::new(inner).poll_write(cx, buf), #[cfg(feature = "zstd")] CompressedAsyncWriter::Zstd(ref mut inner) => Pin::new(inner).poll_write(cx, buf), #[cfg(feature = "xz")] CompressedAsyncWriter::Xz(ref mut inner) => Pin::new(inner).poll_write(cx, buf), } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { match *self { CompressedAsyncWriter::Stored(ref mut inner) => Pin::new(inner).poll_flush(cx), #[cfg(feature = "deflate")] CompressedAsyncWriter::Deflate(ref mut inner) => Pin::new(inner).poll_flush(cx), #[cfg(feature = "bzip2")] CompressedAsyncWriter::Bz(ref mut inner) => Pin::new(inner).poll_flush(cx), #[cfg(feature = "lzma")] CompressedAsyncWriter::Lzma(ref mut inner) => Pin::new(inner).poll_flush(cx), #[cfg(feature = "zstd")] CompressedAsyncWriter::Zstd(ref mut inner) => Pin::new(inner).poll_flush(cx), #[cfg(feature = "xz")] CompressedAsyncWriter::Xz(ref mut inner) => Pin::new(inner).poll_flush(cx), } } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { match *self { CompressedAsyncWriter::Stored(ref mut inner) => Pin::new(inner).poll_close(cx), #[cfg(feature = "deflate")] CompressedAsyncWriter::Deflate(ref mut inner) => Pin::new(inner).poll_close(cx), #[cfg(feature = "bzip2")] CompressedAsyncWriter::Bz(ref mut inner) => Pin::new(inner).poll_close(cx), #[cfg(feature = "lzma")] CompressedAsyncWriter::Lzma(ref mut inner) => Pin::new(inner).poll_close(cx), #[cfg(feature = "zstd")] CompressedAsyncWriter::Zstd(ref mut inner) => Pin::new(inner).poll_close(cx), #[cfg(feature = "xz")] CompressedAsyncWriter::Xz(ref mut inner) => Pin::new(inner).poll_close(cx), } } } pub struct ShutdownIgnoredWriter(W); impl ShutdownIgnoredWriter { pub fn into_inner(self) -> W { self.0 } } impl AsyncWrite for ShutdownIgnoredWriter { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { Pin::new(&mut self.0).poll_write(cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.0).poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, _: &mut Context) -> Poll> { Poll::Ready(Ok(())) } } async_zip-0.0.18/src/base/write/entry_stream.rs000064400000000000000000000320771046102023000176370ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::write::compressed_writer::CompressedAsyncWriter; use crate::base::write::get_or_put_info_zip_unicode_comment_extra_field_mut; use crate::base::write::get_or_put_info_zip_unicode_path_extra_field_mut; use crate::base::write::io::offset::AsyncOffsetWriter; use crate::base::write::CentralDirectoryEntry; use crate::base::write::ZipFileWriter; use crate::entry::ZipEntry; use crate::error::{Result, Zip64ErrorCase, ZipError}; use crate::spec::extra_field::ExtraFieldAsBytes; use crate::spec::header::InfoZipUnicodeCommentExtraField; use crate::spec::header::InfoZipUnicodePathExtraField; use crate::spec::header::{ CentralDirectoryRecord, ExtraField, GeneralPurposeFlag, HeaderId, LocalFileHeader, Zip64ExtendedInformationExtraField, }; use crate::string::StringEncoding; use std::io::Error; use std::pin::Pin; use std::task::{Context, Poll}; use crate::base::read::get_zip64_extra_field_mut; use crate::spec::consts::{NON_ZIP64_MAX_NUM_FILES, NON_ZIP64_MAX_SIZE}; use crc32fast::Hasher; use futures_lite::io::{AsyncWrite, AsyncWriteExt}; /// An entry writer which supports the streaming of data (ie. the writing of unknown size or data at runtime). /// /// # Note /// - This writer cannot be manually constructed; instead, use [`ZipFileWriter::write_entry_stream()`]. /// - [`EntryStreamWriter::close()`] must be called before a stream writer goes out of scope. /// - Utilities for working with [`AsyncWrite`] values are provided by [`AsyncWriteExt`]. pub struct EntryStreamWriter<'b, W: AsyncWrite + Unpin> { writer: AsyncOffsetWriter>, cd_entries: &'b mut Vec, entry: ZipEntry, hasher: Hasher, lfh: LocalFileHeader, lfh_offset: u64, data_offset: u64, force_no_zip64: bool, /// To write back to the original writer if zip64 is required. is_zip64: &'b mut bool, precompressed: bool, } impl<'b, W: AsyncWrite + Unpin> EntryStreamWriter<'b, W> { pub(crate) async fn from_raw( writer: &'b mut ZipFileWriter, mut entry: ZipEntry, ) -> Result> { let lfh_offset = writer.writer.offset(); let lfh = EntryStreamWriter::write_lfh(writer, &mut entry).await?; let data_offset = writer.writer.offset(); let force_no_zip64 = writer.force_no_zip64; let cd_entries = &mut writer.cd_entries; let is_zip64 = &mut writer.is_zip64; let writer = AsyncOffsetWriter::new(CompressedAsyncWriter::from_raw(&mut writer.writer, entry.compression(), false)); Ok(EntryStreamWriter { writer, cd_entries, entry, lfh, lfh_offset, data_offset, hasher: Hasher::new(), force_no_zip64, is_zip64, precompressed: false, }) } pub(crate) async fn from_raw_precompressed( writer: &'b mut ZipFileWriter, mut entry: ZipEntry, ) -> Result> { let lfh_offset = writer.writer.offset(); let lfh = EntryStreamWriter::write_lfh(writer, &mut entry).await?; let data_offset = writer.writer.offset(); let force_no_zip64 = writer.force_no_zip64; let cd_entries = &mut writer.cd_entries; let is_zip64 = &mut writer.is_zip64; let writer = AsyncOffsetWriter::new(CompressedAsyncWriter::from_raw(&mut writer.writer, entry.compression(), true)); Ok(EntryStreamWriter { writer, cd_entries, entry, lfh, lfh_offset, data_offset, hasher: Hasher::new(), force_no_zip64, is_zip64, precompressed: true, }) } async fn write_lfh(writer: &'b mut ZipFileWriter, entry: &mut ZipEntry) -> Result { // Always emit a zip64 extended field, even if we don't need it, because we *might* need it. // If we are forcing no zip, we will have to error later if the file is too large. let (lfh_compressed, lfh_uncompressed) = if !writer.force_no_zip64 { if !writer.is_zip64 { writer.is_zip64 = true; } entry.extra_fields.push(ExtraField::Zip64ExtendedInformation(Zip64ExtendedInformationExtraField { header_id: HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD, uncompressed_size: Some(entry.uncompressed_size), compressed_size: Some(entry.compressed_size), relative_header_offset: None, disk_start_number: None, })); (NON_ZIP64_MAX_SIZE, NON_ZIP64_MAX_SIZE) } else { if entry.compressed_size > NON_ZIP64_MAX_SIZE as u64 || entry.uncompressed_size > NON_ZIP64_MAX_SIZE as u64 { return Err(ZipError::Zip64Needed(Zip64ErrorCase::LargeFile)); } (entry.compressed_size as u32, entry.uncompressed_size as u32) }; let utf8_without_alternative = entry.filename().is_utf8_without_alternative() && entry.comment().is_utf8_without_alternative(); if !utf8_without_alternative { if matches!(entry.filename().encoding(), StringEncoding::Utf8) { let u_file_name = entry.filename().as_bytes().to_vec(); if !u_file_name.is_empty() { let basic_crc32 = crc32fast::hash(entry.filename().alternative().unwrap_or_else(|| entry.filename().as_bytes())); let upath_field = get_or_put_info_zip_unicode_path_extra_field_mut(entry.extra_fields.as_mut()); if let InfoZipUnicodePathExtraField::V1 { crc32, unicode } = upath_field { *crc32 = basic_crc32; *unicode = u_file_name; } } } if matches!(entry.comment().encoding(), StringEncoding::Utf8) { let u_comment = entry.comment().as_bytes().to_vec(); if !u_comment.is_empty() { let basic_crc32 = crc32fast::hash(entry.comment().alternative().unwrap_or_else(|| entry.comment().as_bytes())); let ucom_field = get_or_put_info_zip_unicode_comment_extra_field_mut(entry.extra_fields.as_mut()); if let InfoZipUnicodeCommentExtraField::V1 { crc32, unicode } = ucom_field { *crc32 = basic_crc32; *unicode = u_comment; } } } } let filename_basic = entry.filename().alternative().unwrap_or_else(|| entry.filename().as_bytes()); let lfh = LocalFileHeader { compressed_size: lfh_compressed, uncompressed_size: lfh_uncompressed, compression: entry.compression().into(), crc: entry.crc32, extra_field_length: entry .extra_fields() .count_bytes() .try_into() .map_err(|_| ZipError::ExtraFieldTooLarge)?, file_name_length: filename_basic.len().try_into().map_err(|_| ZipError::FileNameTooLarge)?, mod_time: entry.last_modification_date().time, mod_date: entry.last_modification_date().date, version: crate::spec::version::as_needed_to_extract(entry), flags: GeneralPurposeFlag { data_descriptor: true, encrypted: false, filename_unicode: utf8_without_alternative, }, }; writer.writer.write_all(&crate::spec::consts::LFH_SIGNATURE.to_le_bytes()).await?; writer.writer.write_all(&lfh.as_slice()).await?; writer.writer.write_all(filename_basic).await?; writer.writer.write_all(&entry.extra_fields().as_bytes()).await?; Ok(lfh) } /// Consumes this entry writer and completes all closing tasks. /// /// This includes: /// - Finalising the CRC32 hash value for the written data. /// - Calculating the compressed and uncompressed byte sizes. /// - Constructing a central directory header. /// - Pushing that central directory header to the [`ZipFileWriter`]'s store. /// /// Failure to call this function before going out of scope would result in a corrupted ZIP file. pub async fn close(mut self) -> Result<()> { self.writer.close().await?; if !self.precompressed { self.entry.crc32 = self.hasher.finalize(); self.entry.uncompressed_size = self.writer.offset(); } let inner_writer = self.writer.into_inner().into_inner(); let compressed_size = inner_writer.offset() - self.data_offset; let (cdr_compressed_size, cdr_uncompressed_size, lh_offset) = if self.force_no_zip64 { if self.entry.uncompressed_size > NON_ZIP64_MAX_SIZE as u64 || compressed_size > NON_ZIP64_MAX_SIZE as u64 || self.lfh_offset > NON_ZIP64_MAX_SIZE as u64 { return Err(ZipError::Zip64Needed(Zip64ErrorCase::LargeFile)); } (self.entry.uncompressed_size as u32, compressed_size as u32, self.lfh_offset as u32) } else { // When streaming an entry, we are always using a zip64 field. match get_zip64_extra_field_mut(&mut self.entry.extra_fields) { // This case shouldn't be necessary but is included for completeness. None => { self.entry.extra_fields.push(ExtraField::Zip64ExtendedInformation( Zip64ExtendedInformationExtraField { header_id: HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD, uncompressed_size: Some(self.entry.uncompressed_size), compressed_size: Some(compressed_size), relative_header_offset: Some(self.lfh_offset), disk_start_number: None, }, )); } Some(zip64) => { zip64.uncompressed_size = Some(self.entry.uncompressed_size); zip64.compressed_size = Some(compressed_size); zip64.relative_header_offset = Some(self.lfh_offset); } } self.lfh.extra_field_length = self.entry.extra_fields().count_bytes().try_into().map_err(|_| ZipError::ExtraFieldTooLarge)?; (NON_ZIP64_MAX_SIZE, NON_ZIP64_MAX_SIZE, NON_ZIP64_MAX_SIZE) }; inner_writer.write_all(&crate::spec::consts::DATA_DESCRIPTOR_SIGNATURE.to_le_bytes()).await?; inner_writer.write_all(&self.entry.crc32.to_le_bytes()).await?; inner_writer.write_all(&cdr_compressed_size.to_le_bytes()).await?; inner_writer.write_all(&cdr_uncompressed_size.to_le_bytes()).await?; let comment_basic = self.entry.comment().alternative().unwrap_or_else(|| self.entry.comment().as_bytes()); let cdh = CentralDirectoryRecord { compressed_size: cdr_compressed_size, uncompressed_size: cdr_uncompressed_size, crc: self.entry.crc32, v_made_by: crate::spec::version::as_made_by(), v_needed: self.lfh.version, compression: self.lfh.compression, extra_field_length: self.lfh.extra_field_length, file_name_length: self.lfh.file_name_length, file_comment_length: comment_basic.len().try_into().map_err(|_| ZipError::CommentTooLarge)?, mod_time: self.lfh.mod_time, mod_date: self.lfh.mod_date, flags: self.lfh.flags, disk_start: 0, inter_attr: self.entry.internal_file_attribute(), exter_attr: self.entry.external_file_attribute(), lh_offset, }; self.cd_entries.push(CentralDirectoryEntry { header: cdh, entry: self.entry }); // Ensure that we can fit this many files in this archive if forcing no zip64 if self.cd_entries.len() > NON_ZIP64_MAX_NUM_FILES as usize { if self.force_no_zip64 { return Err(ZipError::Zip64Needed(Zip64ErrorCase::TooManyFiles)); } if !*self.is_zip64 { *self.is_zip64 = true; } } Ok(()) } } impl<'a, W: AsyncWrite + Unpin> AsyncWrite for EntryStreamWriter<'a, W> { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { let poll = Pin::new(&mut self.writer).poll_write(cx, buf); if let Poll::Ready(Ok(written)) = poll { self.hasher.update(&buf[0..written]); } poll } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.writer).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { Pin::new(&mut self.writer).poll_close(cx) } } async_zip-0.0.18/src/base/write/entry_whole.rs000064400000000000000000000301701046102023000174520ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use std::borrow::Cow; use crate::base::write::get_or_put_info_zip_unicode_comment_extra_field_mut; use crate::base::write::get_or_put_info_zip_unicode_path_extra_field_mut; use crate::base::write::{CentralDirectoryEntry, ZipFileWriter}; use crate::entry::ZipEntry; use crate::error::{Result, Zip64ErrorCase, ZipError}; use crate::spec::extra_field::Zip64ExtendedInformationExtraFieldBuilder; use crate::spec::header::{InfoZipUnicodeCommentExtraField, InfoZipUnicodePathExtraField}; use crate::spec::{ extra_field::ExtraFieldAsBytes, header::{CentralDirectoryRecord, ExtraField, GeneralPurposeFlag, LocalFileHeader}, Compression, }; use crate::StringEncoding; #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] use futures_lite::io::Cursor; use crate::spec::consts::{NON_ZIP64_MAX_NUM_FILES, NON_ZIP64_MAX_SIZE}; #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] use async_compression::futures::write; use futures_lite::io::{AsyncWrite, AsyncWriteExt}; pub struct EntryWholeWriter<'b, 'c, W: AsyncWrite + Unpin> { writer: &'b mut ZipFileWriter, entry: ZipEntry, data: Cow<'c, [u8]>, builder: Option, lh_offset: u64, precompressed: bool, } impl<'b, 'c, W: AsyncWrite + Unpin> EntryWholeWriter<'b, 'c, W> { pub fn from_raw(writer: &'b mut ZipFileWriter, entry: ZipEntry, data: &'c [u8]) -> Self { Self { writer, entry, data: Cow::Borrowed(data), builder: None, lh_offset: 0, precompressed: false } } pub fn from_precompressed(writer: &'b mut ZipFileWriter, entry: ZipEntry, data: &'c [u8]) -> Self { Self { writer, entry, data: Cow::Borrowed(data), builder: None, lh_offset: 0, precompressed: true } } async fn compress(&mut self) { if self.entry.compression() == Compression::Stored { return; } #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] { let new_data = compress(&self.entry, &self.data).await; self.data = Cow::Owned(new_data); } } fn enforce_zip64_sizes(&mut self) -> Result<()> { let uncompressed_larger = self.entry.uncompressed_size > NON_ZIP64_MAX_SIZE.into(); let compressed_larger = self.entry.compressed_size > NON_ZIP64_MAX_SIZE.into(); if !uncompressed_larger && !compressed_larger { return Ok(()); } self.enforce_zip64()?; // TODO: accept ZipEntry with sizes already set let builder = Zip64ExtendedInformationExtraFieldBuilder::new(); let builder = builder.sizes(self.entry.compressed_size(), self.entry.uncompressed_size()); self.builder = Some(builder); self.entry.uncompressed_size = NON_ZIP64_MAX_SIZE.into(); self.entry.compressed_size = NON_ZIP64_MAX_SIZE.into(); Ok(()) } fn enforce_zip64_offset(&mut self) -> Result<()> { if self.lh_offset <= NON_ZIP64_MAX_SIZE.into() { return Ok(()); } self.enforce_zip64()?; let builder = self.builder.take().unwrap_or(Zip64ExtendedInformationExtraFieldBuilder::new()); let builder = builder.relative_header_offset(self.lh_offset); self.builder = Some(builder); self.lh_offset = NON_ZIP64_MAX_SIZE.into(); Ok(()) } fn enforce_zip64(&mut self) -> Result<()> { if self.writer.force_no_zip64 { return Err(ZipError::Zip64Needed(Zip64ErrorCase::LargeFile)); } if !self.writer.is_zip64 { self.writer.is_zip64 = true; } Ok(()) } fn utf8_without_alternative(&mut self) -> bool { let utf8_without_alternative = self.entry.filename().is_utf8_without_alternative() && self.entry.comment().is_utf8_without_alternative(); if !utf8_without_alternative { if matches!(self.entry.filename().encoding(), StringEncoding::Utf8) { let u_file_name = self.entry.filename().as_bytes().to_vec(); if !u_file_name.is_empty() { let basic_crc32 = crc32fast::hash( self.entry.filename().alternative().unwrap_or_else(|| self.entry.filename().as_bytes()), ); let upath_field = get_or_put_info_zip_unicode_path_extra_field_mut(self.entry.extra_fields.as_mut()); if let InfoZipUnicodePathExtraField::V1 { crc32, unicode } = upath_field { *crc32 = basic_crc32; *unicode = u_file_name; } } } if matches!(self.entry.comment().encoding(), StringEncoding::Utf8) { let u_comment = self.entry.comment().as_bytes().to_vec(); if !u_comment.is_empty() { let basic_crc32 = crc32fast::hash( self.entry.comment().alternative().unwrap_or_else(|| self.entry.comment().as_bytes()), ); let ucom_field = get_or_put_info_zip_unicode_comment_extra_field_mut(self.entry.extra_fields.as_mut()); if let InfoZipUnicodeCommentExtraField::V1 { crc32, unicode } = ucom_field { *crc32 = basic_crc32; *unicode = u_comment; } } } } utf8_without_alternative } pub async fn write(mut self) -> Result<()> { if !self.precompressed { self.entry.uncompressed_size = self.data.len() as u64; self.entry.crc32 = crc32fast::hash(&self.data); self.compress().await; } self.entry.compressed_size = self.data.len() as u64; self.enforce_zip64_sizes()?; self.lh_offset = self.writer.writer.offset(); self.enforce_zip64_offset()?; if let Some(builder) = self.builder { if !builder.eof_only() { self.entry.extra_fields.push(ExtraField::Zip64ExtendedInformation(builder.build()?)); self.builder = None; } else { self.builder = Some(builder); } } let utf8_without_alternative = self.utf8_without_alternative(); let filename_basic = self.entry.filename().alternative().unwrap_or_else(|| self.entry.filename().as_bytes()); let comment_basic = self.entry.comment().alternative().unwrap_or_else(|| self.entry.comment().as_bytes()); let lf_header = LocalFileHeader { compressed_size: self.entry.compressed_size() as u32, uncompressed_size: self.entry.uncompressed_size() as u32, compression: self.entry.compression().into(), crc: self.entry.crc32(), extra_field_length: self .entry .extra_fields() .count_bytes() .try_into() .map_err(|_| ZipError::ExtraFieldTooLarge)?, file_name_length: filename_basic.len().try_into().map_err(|_| ZipError::FileNameTooLarge)?, mod_time: self.entry.last_modification_date().time, mod_date: self.entry.last_modification_date().date, version: crate::spec::version::as_needed_to_extract(&self.entry), flags: GeneralPurposeFlag { data_descriptor: false, encrypted: false, filename_unicode: utf8_without_alternative, }, }; let mut header = CentralDirectoryRecord { v_made_by: crate::spec::version::as_made_by(), v_needed: lf_header.version, compressed_size: lf_header.compressed_size, uncompressed_size: lf_header.uncompressed_size, compression: lf_header.compression, crc: lf_header.crc, extra_field_length: lf_header.extra_field_length, file_name_length: lf_header.file_name_length, file_comment_length: comment_basic.len().try_into().map_err(|_| ZipError::CommentTooLarge)?, mod_time: lf_header.mod_time, mod_date: lf_header.mod_date, flags: lf_header.flags, disk_start: 0, inter_attr: self.entry.internal_file_attribute(), exter_attr: self.entry.external_file_attribute(), lh_offset: self.lh_offset as u32, }; self.writer.writer.write_all(&crate::spec::consts::LFH_SIGNATURE.to_le_bytes()).await?; self.writer.writer.write_all(&lf_header.as_slice()).await?; self.writer.writer.write_all(filename_basic).await?; self.writer.writer.write_all(&self.entry.extra_fields().as_bytes()).await?; self.writer.writer.write_all(&self.data).await?; if let Some(builder1) = self.builder { self.entry.extra_fields.push(ExtraField::Zip64ExtendedInformation(builder1.build()?)); header.extra_field_length = self.entry.extra_fields().count_bytes().try_into().map_err(|_| ZipError::ExtraFieldTooLarge)?; } self.writer.cd_entries.push(CentralDirectoryEntry { header, entry: self.entry }); // Ensure that we can fit this many files in this archive if forcing no zip64 if self.writer.cd_entries.len() > NON_ZIP64_MAX_NUM_FILES as usize { if self.writer.force_no_zip64 { return Err(ZipError::Zip64Needed(Zip64ErrorCase::TooManyFiles)); } if !self.writer.is_zip64 { self.writer.is_zip64 = true; } } Ok(()) } } #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] /// Compresses the data of a ZIP entry using the specified compression method and level. pub async fn compress(entry: &ZipEntry, data: &[u8]) -> Vec { // TODO: Reduce reallocations of Vec by making a lower-bound estimate of the length reduction and // pre-initialising the Vec to that length. Then truncate() to the actual number of bytes written. let level = entry.compression_level; match entry.compression() { #[cfg(feature = "deflate")] Compression::Deflate => { let mut writer = write::DeflateEncoder::with_quality(Cursor::new(Vec::new()), level); writer.write_all(data).await.unwrap(); writer.close().await.unwrap(); writer.into_inner().into_inner() } #[cfg(feature = "deflate64")] Compression::Deflate64 => panic!("compressing deflate64 is not supported"), #[cfg(feature = "bzip2")] Compression::Bz => { let mut writer = write::BzEncoder::with_quality(Cursor::new(Vec::new()), level); writer.write_all(data).await.unwrap(); writer.close().await.unwrap(); writer.into_inner().into_inner() } #[cfg(feature = "lzma")] Compression::Lzma => { let mut writer = write::LzmaEncoder::with_quality(Cursor::new(Vec::new()), level); writer.write_all(data).await.unwrap(); writer.close().await.unwrap(); writer.into_inner().into_inner() } #[cfg(feature = "xz")] Compression::Xz => { let mut writer = write::XzEncoder::with_quality(Cursor::new(Vec::new()), level); writer.write_all(data).await.unwrap(); writer.close().await.unwrap(); writer.into_inner().into_inner() } #[cfg(feature = "zstd")] Compression::Zstd => { let mut writer = write::ZstdEncoder::with_quality(Cursor::new(Vec::new()), level); writer.write_all(data).await.unwrap(); writer.close().await.unwrap(); writer.into_inner().into_inner() } _ => unreachable!(), } } /// Performs a CRC32 checksum on the provided data. pub fn crc32(data: &[u8]) -> u32 { crc32fast::hash(data) } async_zip-0.0.18/src/base/write/io/mod.rs000064400000000000000000000002341046102023000162770ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod offset; async_zip-0.0.18/src/base/write/io/offset.rs000064400000000000000000000035651046102023000170200ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use std::io::{Error, IoSlice}; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::AsyncWrite; use pin_project::pin_project; /// A wrapper around an [`AsyncWrite`] implementation which tracks the current byte offset. #[pin_project(project = OffsetWriterProj)] pub struct AsyncOffsetWriter { #[pin] inner: W, offset: u64, } impl AsyncOffsetWriter where W: AsyncWrite + Unpin, { /// Constructs a new wrapper from an inner [`AsyncWrite`] writer. pub fn new(inner: W) -> Self { Self { inner, offset: 0 } } /// Returns the current byte offset. pub fn offset(&self) -> u64 { self.offset } /// Consumes this wrapper and returns the inner [`AsyncWrite`] writer. pub fn into_inner(self) -> W { self.inner } pub fn inner_mut(&mut self) -> &mut W { &mut self.inner } } impl AsyncWrite for AsyncOffsetWriter where W: AsyncWrite + Unpin, { fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { let this = self.project(); let poll = this.inner.poll_write(cx, buf); if let Poll::Ready(Ok(inner)) = &poll { *this.offset += *inner as u64; } poll } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.project().inner.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.project().inner.poll_close(cx) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { self.project().inner.poll_write_vectored(cx, bufs) } } async_zip-0.0.18/src/base/write/mod.rs000064400000000000000000000277211046102023000157020ustar 00000000000000// Copyright (c) 2021-2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) //! A module which supports writing ZIP files. //! //! # Example //! ### Whole data (u8 slice) //! ```no_run //! # #[cfg(feature = "deflate")] //! # { //! # use async_zip::{Compression, ZipEntryBuilder, base::write::ZipFileWriter}; //! # use async_zip::error::ZipError; //! # //! # async fn run() -> Result<(), ZipError> { //! let mut writer = ZipFileWriter::new(Vec::::new()); //! //! let data = b"This is an example file."; //! let opts = ZipEntryBuilder::new(String::from("foo.txt").into(), Compression::Deflate); //! //! writer.write_entry_whole(opts, data).await?; //! writer.close().await?; //! # Ok(()) //! # } //! # } //! ``` //! ### Stream data (unknown size & data) //! ```no_run //! # #[cfg(feature = "deflate")] //! # { //! # use async_zip::{Compression, ZipEntryBuilder, base::write::ZipFileWriter}; //! # use std::io::Cursor; //! # use async_zip::error::ZipError; //! # use futures_lite::io::AsyncWriteExt; //! # use tokio_util::compat::TokioAsyncWriteCompatExt; //! # //! # async fn run() -> Result<(), ZipError> { //! let mut writer = ZipFileWriter::new(Vec::::new()); //! //! let data = b"This is an example file."; //! let opts = ZipEntryBuilder::new(String::from("bar.txt").into(), Compression::Deflate); //! //! let mut entry_writer = writer.write_entry_stream(opts).await?; //! entry_writer.write_all(data).await.unwrap(); //! //! entry_writer.close().await?; //! writer.close().await?; //! # Ok(()) //! # } //! # } //! ``` pub(crate) mod compressed_writer; pub(crate) mod entry_stream; pub(crate) mod entry_whole; pub(crate) mod io; #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] pub use entry_whole::compress; pub use entry_stream::EntryStreamWriter; pub use entry_whole::crc32; #[cfg(feature = "tokio")] use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt}; use crate::entry::ZipEntry; use crate::error::Result; use crate::spec::extra_field::ExtraFieldAsBytes; use crate::spec::header::{ CentralDirectoryRecord, EndOfCentralDirectoryHeader, ExtraField, InfoZipUnicodeCommentExtraField, InfoZipUnicodePathExtraField, Zip64EndOfCentralDirectoryLocator, Zip64EndOfCentralDirectoryRecord, }; #[cfg(feature = "tokio")] use crate::tokio::write::ZipFileWriter as TokioZipFileWriter; use entry_whole::EntryWholeWriter; use io::offset::AsyncOffsetWriter; use crate::spec::consts::{NON_ZIP64_MAX_NUM_FILES, NON_ZIP64_MAX_SIZE}; use futures_lite::io::{AsyncWrite, AsyncWriteExt}; pub(crate) struct CentralDirectoryEntry { pub header: CentralDirectoryRecord, pub entry: ZipEntry, } /// A ZIP file writer which acts over AsyncWrite implementers. /// /// # Note /// - [`ZipFileWriter::close()`] must be called before a stream writer goes out of scope. pub struct ZipFileWriter { pub(crate) writer: AsyncOffsetWriter, pub(crate) cd_entries: Vec, /// If true, will error if a Zip64 struct must be written. force_no_zip64: bool, /// Whether to write Zip64 end of directory structs. pub(crate) is_zip64: bool, comment_opt: Option, } impl ZipFileWriter { /// Construct a new ZIP file writer from a mutable reference to a writer. pub fn new(writer: W) -> Self { Self { writer: AsyncOffsetWriter::new(writer), cd_entries: Vec::new(), comment_opt: None, is_zip64: false, force_no_zip64: false, } } /// Force the ZIP writer to operate in non-ZIP64 mode. /// If any files would need ZIP64, an error will be raised. pub fn force_no_zip64(mut self) -> Self { self.force_no_zip64 = true; self } /// Force the ZIP writer to emit Zip64 structs at the end of the archive. /// Zip64 extended fields will only be written if needed. pub fn force_zip64(mut self) -> Self { self.is_zip64 = true; self } /// Write a new ZIP entry of known size and data. pub async fn write_entry_whole>(&mut self, entry: E, data: &[u8]) -> Result<()> { EntryWholeWriter::from_raw(self, entry.into(), data).write().await } /// Write a new ZIP entry of known size and data, with the data already being compressed. /// /// The provided entry's compression method, CRC, and uncompressed size must be set. Use with `base::write::compress` /// and `base::write::crc32` to precompress. pub async fn write_entry_whole_precompressed>(&mut self, entry: E, data: &[u8]) -> Result<()> { EntryWholeWriter::from_precompressed(self, entry.into(), data).write().await } /// Write an entry of unknown size and data via streaming (ie. using a data descriptor). /// The generated Local File Header will be invalid, with no compressed size, uncompressed size, /// and a null CRC. This might cause problems with the destination reader. pub async fn write_entry_stream>(&mut self, entry: E) -> Result> { EntryStreamWriter::from_raw(self, entry.into()).await } /// Write an entry of unknown size and data via streaming (ie. using a data descriptor), with the data already being compressed. /// /// The provided entry's compression method, CRC, and uncompressed size must be set. Use with `base::write::compress` /// and `base::write::crc32` to precompress. /// /// The generated Local File Header will be invalid, with no compressed size, uncompressed size, /// and a null CRC. This might cause problems with the destination reader. pub async fn write_entry_stream_precompressed>( &mut self, entry: E, ) -> Result> { EntryStreamWriter::from_raw_precompressed(self, entry.into()).await } /// Set the ZIP file comment. pub fn comment(&mut self, comment: String) { self.comment_opt = Some(comment); } /// Returns a mutable reference to the inner writer. /// /// Care should be taken when using this inner writer as doing so may invalidate internal state of this writer. pub fn inner_mut(&mut self) -> &mut W { self.writer.inner_mut() } /// Consumes this ZIP writer and completes all closing tasks. /// /// This includes: /// - Writing all central directory headers. /// - Writing the end of central directory header. /// - Writing the file comment. /// /// Failure to call this function before going out of scope would result in a corrupted ZIP file. pub async fn close(mut self) -> Result { let cd_offset = self.writer.offset(); for entry in &self.cd_entries { let filename_basic = entry.entry.filename().alternative().unwrap_or_else(|| entry.entry.filename().as_bytes()); let comment_basic = entry.entry.comment().alternative().unwrap_or_else(|| entry.entry.comment().as_bytes()); self.writer.write_all(&crate::spec::consts::CDH_SIGNATURE.to_le_bytes()).await?; self.writer.write_all(&entry.header.as_slice()).await?; self.writer.write_all(filename_basic).await?; self.writer.write_all(&entry.entry.extra_fields().as_bytes()).await?; self.writer.write_all(comment_basic).await?; } let central_directory_size = self.writer.offset() - cd_offset; let central_directory_size_u32 = if central_directory_size > NON_ZIP64_MAX_SIZE as u64 { NON_ZIP64_MAX_SIZE } else { central_directory_size as u32 }; let num_entries_in_directory = self.cd_entries.len() as u64; let num_entries_in_directory_u16 = if num_entries_in_directory > NON_ZIP64_MAX_NUM_FILES as u64 { NON_ZIP64_MAX_NUM_FILES } else { num_entries_in_directory as u16 }; let cd_offset_u32 = if cd_offset > NON_ZIP64_MAX_SIZE as u64 { if self.force_no_zip64 { return Err(crate::error::ZipError::Zip64Needed(crate::error::Zip64ErrorCase::LargeFile)); } else { self.is_zip64 = true; } NON_ZIP64_MAX_SIZE } else { cd_offset as u32 }; // Add the zip64 EOCDR and EOCDL if we are in zip64 mode. if self.is_zip64 { let eocdr_offset = self.writer.offset(); let eocdr = Zip64EndOfCentralDirectoryRecord { size_of_zip64_end_of_cd_record: 44, version_made_by: crate::spec::version::as_made_by(), version_needed_to_extract: 46, disk_number: 0, disk_number_start_of_cd: 0, num_entries_in_directory_on_disk: num_entries_in_directory, num_entries_in_directory, directory_size: central_directory_size, offset_of_start_of_directory: cd_offset, }; self.writer.write_all(&crate::spec::consts::ZIP64_EOCDR_SIGNATURE.to_le_bytes()).await?; self.writer.write_all(&eocdr.as_bytes()).await?; let eocdl = Zip64EndOfCentralDirectoryLocator { number_of_disk_with_start_of_zip64_end_of_central_directory: 0, relative_offset: eocdr_offset, total_number_of_disks: 1, }; self.writer.write_all(&crate::spec::consts::ZIP64_EOCDL_SIGNATURE.to_le_bytes()).await?; self.writer.write_all(&eocdl.as_bytes()).await?; } let header = EndOfCentralDirectoryHeader { disk_num: 0, start_cent_dir_disk: 0, num_of_entries_disk: num_entries_in_directory_u16, num_of_entries: num_entries_in_directory_u16, size_cent_dir: central_directory_size_u32, cent_dir_offset: cd_offset_u32, file_comm_length: self.comment_opt.as_ref().map(|v| v.len() as u16).unwrap_or_default(), }; self.writer.write_all(&crate::spec::consts::EOCDR_SIGNATURE.to_le_bytes()).await?; self.writer.write_all(&header.as_slice()).await?; if let Some(comment) = self.comment_opt { self.writer.write_all(comment.as_bytes()).await?; } Ok(self.writer.into_inner()) } } #[cfg(feature = "tokio")] impl ZipFileWriter> where W: tokio::io::AsyncWrite + Unpin, { /// Construct a new ZIP file writer from a mutable reference to a writer. pub fn with_tokio(writer: W) -> TokioZipFileWriter { Self { writer: AsyncOffsetWriter::new(writer.compat_write()), cd_entries: Vec::new(), comment_opt: None, is_zip64: false, force_no_zip64: false, } } } pub(crate) fn get_or_put_info_zip_unicode_path_extra_field_mut( extra_fields: &mut Vec, ) -> &mut InfoZipUnicodePathExtraField { if !extra_fields.iter().any(|field| matches!(field, ExtraField::InfoZipUnicodePath(_))) { extra_fields .push(ExtraField::InfoZipUnicodePath(InfoZipUnicodePathExtraField::V1 { crc32: 0, unicode: vec![] })); } for field in extra_fields.iter_mut() { if let ExtraField::InfoZipUnicodePath(extra_field) = field { return extra_field; } } panic!("InfoZipUnicodePathExtraField not found after insertion") } pub(crate) fn get_or_put_info_zip_unicode_comment_extra_field_mut( extra_fields: &mut Vec, ) -> &mut InfoZipUnicodeCommentExtraField { if !extra_fields.iter().any(|field| matches!(field, ExtraField::InfoZipUnicodeComment(_))) { extra_fields .push(ExtraField::InfoZipUnicodeComment(InfoZipUnicodeCommentExtraField::V1 { crc32: 0, unicode: vec![] })); } for field in extra_fields.iter_mut() { if let ExtraField::InfoZipUnicodeComment(extra_field) = field { return extra_field; } } panic!("InfoZipUnicodeCommentExtraField not found after insertion") } async_zip-0.0.18/src/date/builder.rs000064400000000000000000000045071046102023000154170ustar 00000000000000// Copyright (c) 2024 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::ZipDateTime; /// A builder for [`ZipDateTime`]. pub struct ZipDateTimeBuilder(pub(crate) ZipDateTime); impl From for ZipDateTimeBuilder { fn from(date: ZipDateTime) -> Self { Self(date) } } impl Default for ZipDateTimeBuilder { fn default() -> Self { Self::new() } } impl ZipDateTimeBuilder { /// Constructs a new builder which defines the raw underlying data of a ZIP entry. pub fn new() -> Self { Self(ZipDateTime { date: 0, time: 0 }) } /// Sets the date and time's year. pub fn year(mut self, year: i32) -> Self { let year: u16 = (((year - 1980) << 9) & 0xFE00).try_into().unwrap(); self.0.date |= year; self } /// Sets the date and time's month. pub fn month(mut self, month: u32) -> Self { let month: u16 = ((month << 5) & 0x1E0).try_into().unwrap(); self.0.date |= month; self } /// Sets the date and time's day. pub fn day(mut self, day: u32) -> Self { let day: u16 = (day & 0x1F).try_into().unwrap(); self.0.date |= day; self } /// Sets the date and time's hour. pub fn hour(mut self, hour: u32) -> Self { let hour: u16 = ((hour << 11) & 0xF800).try_into().unwrap(); self.0.time |= hour; self } /// Sets the date and time's minute. pub fn minute(mut self, minute: u32) -> Self { let minute: u16 = ((minute << 5) & 0x7E0).try_into().unwrap(); self.0.time |= minute; self } /// Sets the date and time's second. /// /// Note that MS-DOS has a maximum granularity of two seconds. pub fn second(mut self, second: u32) -> Self { let second: u16 = ((second >> 1) & 0x1F).try_into().unwrap(); self.0.time |= second; self } /// Consumes this builder and returns a final [`ZipDateTime`]. /// /// This is equivalent to: /// ``` /// # use async_zip::{ZipDateTime, ZipDateTimeBuilder, Compression}; /// # /// # let builder = ZipDateTimeBuilder::new().year(2024).month(3).day(2); /// let date: ZipDateTime = builder.into(); /// ``` pub fn build(self) -> ZipDateTime { self.into() } } async_zip-0.0.18/src/date/mod.rs000064400000000000000000000063401046102023000145450ustar 00000000000000// Copyright (c) 2021-2024 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub mod builder; #[cfg(feature = "chrono")] use chrono::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Utc}; use self::builder::ZipDateTimeBuilder; // 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() } /// Constructs chrono's [`DateTime`] representation of this date & time. /// /// Note that this requires the `chrono` feature. #[cfg(feature = "chrono")] pub fn as_chrono(&self) -> LocalResult> { self.into() } /// Constructs this date & time from chrono's [`DateTime`] representation. /// /// Note that this requires the `chrono` feature. #[cfg(feature = "chrono")] pub fn from_chrono(dt: &DateTime) -> Self { dt.into() } } impl From for ZipDateTime { fn from(builder: ZipDateTimeBuilder) -> Self { builder.0 } } #[cfg(feature = "chrono")] impl From<&DateTime> for ZipDateTime { fn from(value: &DateTime) -> Self { let mut builder = ZipDateTimeBuilder::new(); builder = builder.year(value.date_naive().year()); builder = builder.month(value.date_naive().month()); builder = builder.day(value.date_naive().day()); builder = builder.hour(value.time().hour()); builder = builder.minute(value.time().minute()); builder = builder.second(value.time().second()); builder.build() } } #[cfg(feature = "chrono")] impl From<&ZipDateTime> for LocalResult> { fn from(value: &ZipDateTime) -> Self { Utc.with_ymd_and_hms(value.year(), value.month(), value.day(), value.hour(), value.minute(), value.second()) } } #[cfg(feature = "chrono")] impl From> for ZipDateTime { fn from(value: DateTime) -> Self { (&value).into() } } #[cfg(feature = "chrono")] impl From for LocalResult> { fn from(value: ZipDateTime) -> Self { (&value).into() } } async_zip-0.0.18/src/entry/builder.rs000064400000000000000000000100701046102023000156330ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::entry::ZipEntry; use crate::spec::{attribute::AttributeCompatibility, header::ExtraField, Compression}; use crate::{date::ZipDateTime, string::ZipString}; /// A builder for [`ZipEntry`]. pub struct ZipEntryBuilder(pub(crate) ZipEntry); impl From for ZipEntryBuilder { fn from(entry: ZipEntry) -> Self { Self(entry) } } impl ZipEntryBuilder { /// Constructs a new builder which defines the raw underlying data of a ZIP entry. /// /// A filename and compression method are needed to construct the builder as minimal parameters. pub fn new(filename: ZipString, compression: Compression) -> Self { Self(ZipEntry::new(filename, compression)) } /// Sets the entry's filename. pub fn filename(mut self, filename: ZipString) -> Self { self.0.filename = filename; self } /// Sets the entry's compression method. pub fn compression(mut self, compression: Compression) -> Self { self.0.compression = compression; self } // Sets the entry's CRC32 checksum. pub fn crc32>(mut self, crc: N) -> Self { self.0.crc32 = crc.into(); self } // Sets the entry's compressed size. pub fn compressed_size>(mut self, size: N) -> Self { self.0.compressed_size = size.into(); self } // Sets the entry's uncompressed size. pub fn uncompressed_size>(mut self, size: N) -> Self { self.0.uncompressed_size = size.into(); self } /// Set the deflate compression option. /// /// If the compression type isn't deflate, this option has no effect. #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] pub fn deflate_option(mut self, option: crate::DeflateOption) -> Self { self.0.compression_level = option.into_level(); self } /// Sets the entry's attribute host compatibility. pub fn attribute_compatibility(mut self, compatibility: AttributeCompatibility) -> Self { self.0.attribute_compatibility = compatibility; self } /// Sets the entry's last modification date. pub fn last_modification_date(mut self, date: ZipDateTime) -> Self { self.0.last_modification_date = date; self } /// Sets the entry's internal file attribute. pub fn internal_file_attribute(mut self, attribute: u16) -> Self { self.0.internal_file_attribute = attribute; self } /// Sets the entry's external file attribute. pub fn external_file_attribute(mut self, attribute: u32) -> Self { self.0.external_file_attribute = attribute; self } /// Sets the entry's extra field data. pub fn extra_fields(mut self, field: Vec) -> Self { self.0.extra_fields = field; self } /// Sets the entry's file comment. pub fn comment(mut self, comment: ZipString) -> Self { self.0.comment = comment; self } /// Sets the entry's Unix permissions mode. /// /// If the attribute host compatibility isn't set to Unix, this will have no effect. pub fn unix_permissions(mut self, mode: u16) -> Self { if matches!(self.0.attribute_compatibility, AttributeCompatibility::Unix) { self.0.external_file_attribute = (self.0.external_file_attribute & 0xFFFF) | (mode as u32) << 16; } self } /// Returns a reference to the currently built entry. pub fn current(&self) -> &ZipEntry { &self.0 } /// Consumes this builder and returns a final [`ZipEntry`]. /// /// This is equivalent to: /// ``` /// # use async_zip::{ZipEntry, ZipEntryBuilder, Compression}; /// # /// # let builder = ZipEntryBuilder::new(String::from("foo.bar").into(), Compression::Stored); /// let entry: ZipEntry = builder.into(); /// ``` pub fn build(self) -> ZipEntry { self.into() } } async_zip-0.0.18/src/entry/mod.rs000064400000000000000000000160101046102023000147640ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub mod builder; use std::ops::Deref; use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom}; use crate::entry::builder::ZipEntryBuilder; 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. /// /// This type cannot be directly constructed so instead, the [`ZipEntryBuilder`] must be used. Internally this builder /// stores a [`ZipEntry`] so conversions between these two types via the [`From`] implementations will be /// non-allocating. #[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, } impl From for ZipEntry { fn from(builder: ZipEntryBuilder) -> Self { builder.0 } } impl ZipEntry { pub(crate) fn new(filename: ZipString, compression: Compression) -> Self { ZipEntry { filename, compression, #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] compression_level: async_compression::Level::Default, crc32: 0, uncompressed_size: 0, compressed_size: 0, attribute_compatibility: AttributeCompatibility::Unix, last_modification_date: ZipDateTime::default(), internal_file_attribute: 0, external_file_attribute: 0, extra_fields: Vec::new(), comment: String::new().into(), data_descriptor: false, } } /// 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('/')) } } /// 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 } } async_zip-0.0.18/src/error.rs000064400000000000000000000051621046102023000142030ustar 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 only {1} bytes remain")] InvalidExtraFieldHeader(u16, usize), #[error("zip64 extended information field was incomplete")] Zip64ExtendedFieldIncomplete, #[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, } async_zip-0.0.18/src/file/builder.rs000064400000000000000000000021211046102023000154070ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::{file::ZipFile, string::ZipString}; /// A builder for [`ZipFile`]. pub struct ZipFileBuilder(pub(crate) ZipFile); impl From for ZipFileBuilder { fn from(file: ZipFile) -> Self { Self(file) } } impl Default for ZipFileBuilder { fn default() -> Self { ZipFileBuilder(ZipFile { entries: Vec::new(), zip64: false, comment: String::new().into() }) } } impl ZipFileBuilder { pub fn new() -> Self { Self::default() } /// Sets the file's comment. pub fn comment(mut self, comment: ZipString) -> Self { self.0.comment = comment; self } /// Consumes this builder and returns a final [`ZipFile`]. /// /// This is equivalent to: /// ``` /// # use async_zip::{ZipFile, ZipFileBuilder}; /// # /// # let builder = ZipFileBuilder::new(); /// let file: ZipFile = builder.into(); /// ``` pub fn build(self) -> ZipFile { self.into() } } async_zip-0.0.18/src/file/mod.rs000064400000000000000000000016471046102023000145540ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod builder; use crate::{entry::StoredZipEntry, string::ZipString}; use builder::ZipFileBuilder; /// 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 From for ZipFile { fn from(builder: ZipFileBuilder) -> Self { builder.0 } } 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 } } async_zip-0.0.18/src/lib.rs000064400000000000000000000044231046102023000136170ustar 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::{builder::ZipDateTimeBuilder, ZipDateTime}; pub use crate::entry::{builder::ZipEntryBuilder, StoredZipEntry, ZipEntry}; pub use crate::file::{builder::ZipFileBuilder, ZipFile}; pub use crate::string::{StringEncoding, ZipString}; async_zip-0.0.18/src/spec/attribute.rs000064400000000000000000000026551046102023000160130ustar 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() } } async_zip-0.0.18/src/spec/compression.rs000064400000000000000000000066471046102023000163560ustar 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}; #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] use async_compression::Level; /// 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), } #[cfg(any(feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz"))] impl DeflateOption { pub(crate) fn into_level(self) -> Level { // FIXME: There's no clear documentation on what these specific levels defined in the ZIP specification relate // to. We want to be compatible with any other library, and not specific to `async_compression`'s levels. if let Self::Other(l) = self { Level::Precise(l) } else { Level::Default } } } async_zip-0.0.18/src/spec/consts.rs000064400000000000000000000034541046102023000153170ustar 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; /// The maximum number of files or disks in a ZIP file before it requires ZIP64. pub const NON_ZIP64_MAX_NUM_FILES: u16 = 0xFFFF; // 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; async_zip-0.0.18/src/spec/extra_field.rs000064400000000000000000000260461046102023000162760ustar 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; pub(crate) trait ExtraFieldAsBytes { fn as_bytes(&self) -> Vec; fn count_bytes(&self) -> usize; } impl ExtraFieldAsBytes for &[ExtraField] { fn as_bytes(&self) -> Vec { let mut buffer = Vec::new(); for field in self.iter() { buffer.append(&mut field.as_bytes()); } buffer } fn count_bytes(&self) -> usize { self.iter().map(|field| field.count_bytes()).sum() } } impl ExtraFieldAsBytes for ExtraField { fn as_bytes(&self) -> Vec { match self { ExtraField::Zip64ExtendedInformation(field) => field.as_bytes(), ExtraField::InfoZipUnicodeComment(field) => field.as_bytes(), ExtraField::InfoZipUnicodePath(field) => field.as_bytes(), ExtraField::Unknown(field) => field.as_bytes(), } } fn count_bytes(&self) -> usize { match self { ExtraField::Zip64ExtendedInformation(field) => field.count_bytes(), ExtraField::InfoZipUnicodeComment(field) => field.count_bytes(), ExtraField::InfoZipUnicodePath(field) => field.count_bytes(), ExtraField::Unknown(field) => field.count_bytes(), } } } impl ExtraFieldAsBytes for UnknownExtraField { fn as_bytes(&self) -> Vec { let mut bytes = Vec::new(); let header_id: u16 = self.header_id.into(); bytes.append(&mut header_id.to_le_bytes().to_vec()); bytes.append(&mut self.data_size.to_le_bytes().to_vec()); bytes.append(&mut self.content.clone()); bytes } fn count_bytes(&self) -> usize { 4 + self.content.len() } } impl ExtraFieldAsBytes for Zip64ExtendedInformationExtraField { fn as_bytes(&self) -> Vec { let mut bytes = Vec::new(); let header_id: u16 = self.header_id.into(); bytes.append(&mut header_id.to_le_bytes().to_vec()); bytes.append(&mut (self.content_size() as u16).to_le_bytes().to_vec()); if let Some(uncompressed_size) = &self.uncompressed_size { bytes.append(&mut uncompressed_size.to_le_bytes().to_vec()); } if let Some(compressed_size) = &self.compressed_size { bytes.append(&mut compressed_size.to_le_bytes().to_vec()); } if let Some(relative_header_offset) = &self.relative_header_offset { bytes.append(&mut relative_header_offset.to_le_bytes().to_vec()); } if let Some(disk_start_number) = &self.disk_start_number { bytes.append(&mut disk_start_number.to_le_bytes().to_vec()); } bytes } fn count_bytes(&self) -> usize { 4 + self.content_size() } } impl ExtraFieldAsBytes for InfoZipUnicodeCommentExtraField { fn as_bytes(&self) -> Vec { let mut bytes = Vec::new(); let header_id: u16 = HeaderId::INFO_ZIP_UNICODE_COMMENT_EXTRA_FIELD.into(); bytes.append(&mut header_id.to_le_bytes().to_vec()); match self { InfoZipUnicodeCommentExtraField::V1 { crc32, unicode } => { let data_size: u16 = (5 + unicode.len()).try_into().unwrap(); bytes.append(&mut data_size.to_le_bytes().to_vec()); bytes.push(1); bytes.append(&mut crc32.to_le_bytes().to_vec()); bytes.append(&mut unicode.clone()); } InfoZipUnicodeCommentExtraField::Unknown { version, data } => { let data_size: u16 = (1 + data.len()).try_into().unwrap(); bytes.append(&mut data_size.to_le_bytes().to_vec()); bytes.push(*version); bytes.append(&mut data.clone()); } } bytes } fn count_bytes(&self) -> usize { match self { InfoZipUnicodeCommentExtraField::V1 { unicode, .. } => 9 + unicode.len(), InfoZipUnicodeCommentExtraField::Unknown { data, .. } => 5 + data.len(), } } } impl ExtraFieldAsBytes for InfoZipUnicodePathExtraField { fn as_bytes(&self) -> Vec { let mut bytes = Vec::new(); let header_id: u16 = HeaderId::INFO_ZIP_UNICODE_PATH_EXTRA_FIELD.into(); bytes.append(&mut header_id.to_le_bytes().to_vec()); match self { InfoZipUnicodePathExtraField::V1 { crc32, unicode } => { let data_size: u16 = (5 + unicode.len()).try_into().unwrap(); bytes.append(&mut data_size.to_le_bytes().to_vec()); bytes.push(1); bytes.append(&mut crc32.to_le_bytes().to_vec()); bytes.append(&mut unicode.clone()); } InfoZipUnicodePathExtraField::Unknown { version, data } => { let data_size: u16 = (1 + data.len()).try_into().unwrap(); bytes.append(&mut data_size.to_le_bytes().to_vec()); bytes.push(*version); bytes.append(&mut data.clone()); } } bytes } fn count_bytes(&self) -> usize { match self { InfoZipUnicodePathExtraField::V1 { unicode, .. } => 9 + unicode.len(), InfoZipUnicodePathExtraField::Unknown { data, .. } => 5 + data.len(), } } } /// 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], uncompressed_size: u32, compressed_size: u32, ) -> 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 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 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 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 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 }; Ok(Zip64ExtendedInformationExtraField { header_id, 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, ) -> 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)?, )), 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() })), } } pub struct Zip64ExtendedInformationExtraFieldBuilder { field: Zip64ExtendedInformationExtraField, } impl Zip64ExtendedInformationExtraFieldBuilder { pub fn new() -> Self { Self { field: Zip64ExtendedInformationExtraField { header_id: HeaderId::ZIP64_EXTENDED_INFORMATION_EXTRA_FIELD, uncompressed_size: None, compressed_size: None, relative_header_offset: None, disk_start_number: None, }, } } pub fn sizes(mut self, compressed_size: u64, uncompressed_size: u64) -> Self { self.field.compressed_size = Some(compressed_size); self.field.uncompressed_size = Some(uncompressed_size); self } pub fn relative_header_offset(mut self, relative_header_offset: u64) -> Self { self.field.relative_header_offset = Some(relative_header_offset); self } #[allow(dead_code)] pub fn disk_start_number(mut self, disk_start_number: u32) -> Self { self.field.disk_start_number = Some(disk_start_number); self } pub fn eof_only(&self) -> bool { (self.field.uncompressed_size.is_none() && self.field.compressed_size.is_none()) && (self.field.relative_header_offset.is_some() || self.field.disk_start_number.is_some()) } pub fn build(self) -> ZipResult { let field = self.field; if field.content_size() == 0 { return Err(ZipError::Zip64ExtendedFieldIncomplete); } Ok(field) } } async_zip-0.0.18/src/spec/header.rs000064400000000000000000000125731046102023000152400ustar 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), } /// 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 header_id: HeaderId, 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, } impl Zip64ExtendedInformationExtraField { pub(crate) fn content_size(&self) -> usize { self.uncompressed_size.map(|_| 8).unwrap_or_default() + self.compressed_size.map(|_| 8).unwrap_or_default() + self.relative_header_offset.map(|_| 8).unwrap_or_default() + self.disk_start_number.map(|_| 8).unwrap_or_default() } } /// 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, } // 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, } async_zip-0.0.18/src/spec/mod.rs000064400000000000000000000005261046102023000145620ustar 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 extra_field; pub(crate) mod header; pub(crate) mod parse; pub(crate) mod version; pub use compression::Compression; async_zip-0.0.18/src/spec/parse.rs000064400000000000000000000351051046102023000151160ustar 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 LocalFileHeader { pub fn as_slice(&self) -> [u8; 26] { let mut array = [0; 26]; let mut cursor = 0; array_push!(array, cursor, self.version.to_le_bytes()); array_push!(array, cursor, self.flags.as_slice()); array_push!(array, cursor, self.compression.to_le_bytes()); array_push!(array, cursor, self.mod_time.to_le_bytes()); array_push!(array, cursor, self.mod_date.to_le_bytes()); 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_push!(array, cursor, self.file_name_length.to_le_bytes()); array_push!(array, cursor, self.extra_field_length.to_le_bytes()); array } } impl GeneralPurposeFlag { pub fn as_slice(&self) -> [u8; 2] { let encrypted: u16 = match self.encrypted { false => 0x0, true => 0b1, }; let data_descriptor: u16 = match self.data_descriptor { false => 0x0, true => 0x8, }; let filename_unicode: u16 = match self.filename_unicode { false => 0x0, true => 0x800, }; (encrypted | data_descriptor | filename_unicode).to_le_bytes() } } impl CentralDirectoryRecord { pub fn as_slice(&self) -> [u8; 42] { let mut array = [0; 42]; let mut cursor = 0; array_push!(array, cursor, self.v_made_by.to_le_bytes()); array_push!(array, cursor, self.v_needed.to_le_bytes()); array_push!(array, cursor, self.flags.as_slice()); array_push!(array, cursor, self.compression.to_le_bytes()); array_push!(array, cursor, self.mod_time.to_le_bytes()); array_push!(array, cursor, self.mod_date.to_le_bytes()); 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_push!(array, cursor, self.file_name_length.to_le_bytes()); array_push!(array, cursor, self.extra_field_length.to_le_bytes()); array_push!(array, cursor, self.file_comment_length.to_le_bytes()); array_push!(array, cursor, self.disk_start.to_le_bytes()); array_push!(array, cursor, self.inter_attr.to_le_bytes()); array_push!(array, cursor, self.exter_attr.to_le_bytes()); array_push!(array, cursor, self.lh_offset.to_le_bytes()); array } } impl EndOfCentralDirectoryHeader { pub fn as_slice(&self) -> [u8; 18] { let mut array = [0; 18]; let mut cursor = 0; array_push!(array, cursor, self.disk_num.to_le_bytes()); array_push!(array, cursor, self.start_cent_dir_disk.to_le_bytes()); array_push!(array, cursor, self.num_of_entries_disk.to_le_bytes()); array_push!(array, cursor, self.num_of_entries.to_le_bytes()); array_push!(array, cursor, self.size_cent_dir.to_le_bytes()); array_push!(array, cursor, self.cent_dir_offset.to_le_bytes()); array_push!(array, cursor, self.file_comm_length.to_le_bytes()); array } } 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 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 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))) } pub fn as_bytes(&self) -> [u8; 16] { let mut array = [0; 16]; let mut cursor = 0; array_push!(array, cursor, self.number_of_disk_with_start_of_zip64_end_of_central_directory.to_le_bytes()); array_push!(array, cursor, self.relative_offset.to_le_bytes()); array_push!(array, cursor, self.total_number_of_disks.to_le_bytes()); array } } /// Parse the extra fields. pub fn parse_extra_fields(data: Vec, uncompressed_size: u32, compressed_size: u32) -> 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, data.len() - cursor - 8 - field_size as usize)); } let data = &data[cursor + 4..cursor + 4 + field_size as usize]; extra_fields.push(extra_field_from_bytes(header_id, field_size, data, uncompressed_size, compressed_size)?); 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::ZIP64_EOCDL_SIGNATURE; 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, } ) } } async_zip-0.0.18/src/spec/version.rs000064400000000000000000000022201046102023000154610ustar 00000000000000// Copyright (c) 2021 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::entry::ZipEntry; #[cfg(any( feature = "deflate", feature = "bzip2", feature = "zstd", feature = "lzma", feature = "xz", feature = "deflate64" ))] use crate::spec::Compression; pub(crate) const SPEC_VERSION_MADE_BY: u16 = 63; // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#443 pub fn as_needed_to_extract(entry: &ZipEntry) -> u16 { let mut version = match entry.compression() { #[cfg(feature = "deflate")] Compression::Deflate => 20, #[cfg(feature = "deflate64")] Compression::Deflate64 => 21, #[cfg(feature = "bzip2")] Compression::Bz => 46, #[cfg(feature = "lzma")] Compression::Lzma => 63, _ => 10, }; if let Ok(true) = entry.dir() { version = std::cmp::max(version, 20); } version } // https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#442 pub fn as_made_by() -> u16 { // Default to UNIX mapping for the moment. 3 << 8 | SPEC_VERSION_MADE_BY } async_zip-0.0.18/src/string.rs000064400000000000000000000075321046102023000143630ustar 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 } } } async_zip-0.0.18/src/tests/combined/mod.rs000064400000000000000000000002041046102023000165430ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) async_zip-0.0.18/src/tests/mod.rs000064400000000000000000000007651046102023000147770ustar 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; pub(crate) mod spec; pub(crate) mod write; 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()); } async_zip-0.0.18/src/tests/read/compression/bzip2.data000064400000000000000000000020551046102023000210010ustar 00000000000000BZh61AY&SY3n@1 "h0  "(Hr7async_zip-0.0.18/src/tests/read/compression/deflate.data000064400000000000000000000020111046102023000213470ustar 00000000000000KWHJ,async_zip-0.0.18/src/tests/read/compression/lzma.data000064400000000000000000000020361046102023000207150ustar 00000000000000]3@b1 %/툀async_zip-0.0.18/src/tests/read/compression/mod.rs000064400000000000000000000031731046102023000202470ustar 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, $type: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, $type); 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; async_zip-0.0.18/src/tests/read/compression/xz.data000064400000000000000000000021001046102023000204030ustar 000000000000007zXZִF!t/foo bar"P .s}YZasync_zip-0.0.18/src/tests/read/compression/zstd.data000064400000000000000000000020201046102023000207270ustar 00000000000000(/X9foo barasync_zip-0.0.18/src/tests/read/locator/empty-buffer-boundary.zip000064400000000000000000000040241046102023000231720ustar 00000000000000PKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAasync_zip-0.0.18/src/tests/read/locator/empty-with-max-comment.zip000064400000000000000000002000251046102023000232750ustar 00000000000000PKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAasync_zip-0.0.18/src/tests/read/locator/empty.zip000064400000000000000000000000261046102023000201000ustar 00000000000000PKasync_zip-0.0.18/src/tests/read/locator/mod.rs000064400000000000000000000036001046102023000173440ustar 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); } async_zip-0.0.18/src/tests/read/mod.rs000064400000000000000000000003171046102023000157030ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod compression; pub(crate) mod locator; pub(crate) mod zip64; async_zip-0.0.18/src/tests/read/zip64/mod.rs000064400000000000000000000067631046102023000166720ustar 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"); } } async_zip-0.0.18/src/tests/read/zip64/zip64.zip000064400000000000000000000003211046102023000172250ustar 00000000000000PK-m+V}- Hello World! PK-m+V} -PK,-/@PKoPK/@async_zip-0.0.18/src/tests/spec/date.rs000064400000000000000000000023141046102023000160570ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) #[cfg(feature = "chrono")] use chrono::{TimeZone, Utc}; use crate::ZipDateTimeBuilder; #[test] #[cfg(feature = "chrono")] fn date_conversion_test_chrono() { let original_dt = Utc.timestamp_opt(1666544102, 0).unwrap(); let zip_dt = crate::ZipDateTime::from_chrono(&original_dt); let result_dt = zip_dt.as_chrono().single().expect("expected single unique result"); assert_eq!(result_dt, original_dt); } #[test] fn date_conversion_test() { let year = 2000; let month = 9; let day = 8; let hour = 7; let minute = 5; let second = 4; let mut builder = ZipDateTimeBuilder::new(); builder = builder.year(year); builder = builder.month(month); builder = builder.day(day); builder = builder.hour(hour); builder = builder.minute(minute); builder = builder.second(second); let built = builder.build(); assert_eq!(year, built.year()); assert_eq!(month, built.month()); assert_eq!(day, built.day()); assert_eq!(hour, built.hour()); assert_eq!(minute, built.minute()); assert_eq!(second, built.second()); } async_zip-0.0.18/src/tests/spec/mod.rs000064400000000000000000000002321046102023000157160ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) pub(crate) mod date; async_zip-0.0.18/src/tests/write/mod.rs000064400000000000000000000016221046102023000161220ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use futures_lite::io::AsyncWrite; use std::io::Error; use std::pin::Pin; use std::task::{Context, Poll}; pub(crate) mod offset; mod zip64; /// /dev/null for AsyncWrite. /// Useful for tests that involve writing, but not reading, large amounts of data. pub(crate) struct AsyncSink; // AsyncSink is always ready to receive bytes and throw them away. impl AsyncWrite for AsyncSink { fn poll_write(self: Pin<&mut Self>, _: &mut Context<'_>, buf: &[u8]) -> Poll> { Poll::Ready(Ok(buf.len())) } fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } async_zip-0.0.18/src/tests/write/offset/mod.rs000064400000000000000000000013561046102023000174140ustar 00000000000000// Copyright (c) 2022 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use crate::base::write::io::offset::AsyncOffsetWriter; #[tokio::test] async fn basic() { use futures_lite::io::AsyncWriteExt; use futures_lite::io::Cursor; let mut writer = AsyncOffsetWriter::new(Cursor::new(Vec::new())); assert_eq!(writer.offset(), 0); writer.write_all(b"Foo. Bar. Foo. Bar.").await.expect("failed to write data"); assert_eq!(writer.offset(), 19); writer.write_all(b"Foo. Foo.").await.expect("failed to write data"); assert_eq!(writer.offset(), 28); writer.write_all(b"Bar. Bar.").await.expect("failed to write data"); assert_eq!(writer.offset(), 37); } async_zip-0.0.18/src/tests/write/zip64/mod.rs000064400000000000000000000227551046102023000171100ustar 00000000000000// Copyright Cognite AS, 2023 use crate::base::write::ZipFileWriter; use crate::error::{Zip64ErrorCase, ZipError}; use crate::spec::consts::NON_ZIP64_MAX_SIZE; use crate::tests::init_logger; use crate::tests::write::AsyncSink; use crate::{Compression, ZipEntryBuilder}; use std::io::Read; use crate::spec::header::ExtraField; use futures_lite::io::AsyncWriteExt; // Useful constants for writing a large file. const BATCH_SIZE: usize = 100_000; const NUM_BATCHES: usize = NON_ZIP64_MAX_SIZE as usize / BATCH_SIZE + 1; const BATCHED_FILE_SIZE: usize = NUM_BATCHES * BATCH_SIZE; /// Test writing a small zip64 file. /// No zip64 extra fields will be emitted for EntryWhole. /// Z64 end of directory record & locator should be emitted #[tokio::test] async fn test_write_zip64_file() { init_logger(); let mut buffer = Vec::new(); let mut writer = ZipFileWriter::new(&mut buffer).force_zip64(); let entry = ZipEntryBuilder::new("file1".to_string().into(), Compression::Stored); writer.write_entry_whole(entry, &[0, 0, 0, 0]).await.unwrap(); let entry = ZipEntryBuilder::new("file2".to_string().into(), Compression::Stored); let mut entry_writer = writer.write_entry_stream(entry).await.unwrap(); entry_writer.write_all(&[0, 0, 0, 0]).await.unwrap(); entry_writer.close().await.unwrap(); writer.close().await.unwrap(); let cursor = std::io::Cursor::new(buffer); let mut zip = zip::read::ZipArchive::new(cursor).unwrap(); let mut file1 = zip.by_name("file1").unwrap(); assert_eq!(file1.extra_data(), Some(&[] as &[u8])); let mut buffer = Vec::new(); file1.read_to_end(&mut buffer).unwrap(); assert_eq!(buffer.as_slice(), &[0, 0, 0, 0]); drop(file1); let mut file2 = zip.by_name("file2").unwrap(); let mut buffer = Vec::new(); file2.read_to_end(&mut buffer).unwrap(); assert_eq!(buffer.as_slice(), &[0, 0, 0, 0]); } /// Test writing a large zip64 file. This test will use upwards of 4GB of memory. #[tokio::test] async fn test_write_large_zip64_file() { init_logger(); // Allocate space with some extra for metadata records let mut buffer = Vec::with_capacity(BATCHED_FILE_SIZE + 100_000); let mut writer = ZipFileWriter::new(&mut buffer); // Stream-written zip files are dubiously spec-conformant. We need to specify a valid file size // in order for rs-zip (and unzip) to correctly read these files. let entry = ZipEntryBuilder::new("file".to_string().into(), Compression::Stored); let entry = entry.uncompressed_size(BATCHED_FILE_SIZE as u64); let entry = entry.compressed_size(BATCHED_FILE_SIZE as u64); let mut entry_writer = writer.write_entry_stream(entry).await.unwrap(); for _ in 0..NUM_BATCHES { entry_writer.write_all(&[0; BATCH_SIZE]).await.unwrap(); } entry_writer.close().await.unwrap(); assert!(writer.is_zip64); let cd_entry = writer.cd_entries.last().unwrap(); match &cd_entry.entry.extra_fields.last().unwrap() { ExtraField::Zip64ExtendedInformation(zip64) => { assert_eq!(zip64.compressed_size.unwrap(), BATCHED_FILE_SIZE as u64); assert_eq!(zip64.uncompressed_size.unwrap(), BATCHED_FILE_SIZE as u64); } e => panic!("Expected a Zip64 extended field, got {:?}", e), } assert_eq!(cd_entry.header.uncompressed_size, NON_ZIP64_MAX_SIZE); assert_eq!(cd_entry.header.compressed_size, NON_ZIP64_MAX_SIZE); writer.close().await.unwrap(); let cursor = std::io::Cursor::new(buffer); let mut archive = zip::read::ZipArchive::new(cursor).unwrap(); let mut file = archive.by_name("file").unwrap(); assert_eq!(file.compression(), zip::CompressionMethod::Stored); assert_eq!(file.size(), BATCHED_FILE_SIZE as u64); let mut buffer = [0; 100_000]; let mut bytes_total = 0; loop { let read_bytes = file.read(&mut buffer).unwrap(); if read_bytes == 0 { break; } bytes_total += read_bytes; } assert_eq!(bytes_total, BATCHED_FILE_SIZE); } /// Test writing a file, and reading it with async-zip #[tokio::test] async fn test_write_large_zip64_file_self_read() { use futures_lite::io::AsyncReadExt; init_logger(); // Allocate space with some extra for metadata records let mut buffer = Vec::with_capacity(BATCHED_FILE_SIZE + 100_000); let mut writer = ZipFileWriter::new(&mut buffer); let entry = ZipEntryBuilder::new("file".into(), Compression::Stored); let mut entry_writer = writer.write_entry_stream(entry).await.unwrap(); for _ in 0..NUM_BATCHES { entry_writer.write_all(&[0; BATCH_SIZE]).await.unwrap(); } entry_writer.close().await.unwrap(); writer.close().await.unwrap(); let reader = crate::base::read::mem::ZipFileReader::new(buffer).await.unwrap(); assert!(reader.file().zip64); assert_eq!(reader.file().entries[0].entry.filename().as_str().unwrap(), "file"); assert_eq!(reader.file().entries[0].entry.compressed_size, BATCHED_FILE_SIZE as u64); let mut entry = reader.reader_without_entry(0).await.unwrap(); let mut buffer = [0; 100_000]; let mut bytes_total = 0; loop { let read_bytes = entry.read(&mut buffer).await.unwrap(); if read_bytes == 0 { break; } bytes_total += read_bytes; } assert_eq!(bytes_total, BATCHED_FILE_SIZE); } /// Test writing a zip64 file with more than u16::MAX files. #[tokio::test] async fn test_write_zip64_file_many_entries() { init_logger(); // The generated file will likely be ~3MB in size. let mut buffer = Vec::with_capacity(3_500_000); let mut writer = ZipFileWriter::new(&mut buffer); for i in 0..=u16::MAX as u32 + 1 { let entry = ZipEntryBuilder::new(i.to_string().into(), Compression::Stored); writer.write_entry_whole(entry, &[]).await.unwrap(); } assert!(writer.is_zip64); writer.close().await.unwrap(); let cursor = std::io::Cursor::new(buffer); let mut zip = zip::read::ZipArchive::new(cursor).unwrap(); assert_eq!(zip.len(), u16::MAX as usize + 2); for i in 0..=u16::MAX as u32 + 1 { let mut file = zip.by_name(&i.to_string()).unwrap(); let mut buf = Vec::new(); file.read_to_end(&mut buf).unwrap(); } } /// Tests that EntryWholeWriter switches to Zip64 mode when writing too many files for a non-Zip64. #[tokio::test] async fn test_zip64_when_many_files_whole() { let mut sink = AsyncSink; let mut writer = ZipFileWriter::new(&mut sink); for i in 0..=u16::MAX as u32 + 1 { let entry = ZipEntryBuilder::new(format!("{i}").into(), Compression::Stored); writer.write_entry_whole(entry, &[]).await.unwrap() } assert!(writer.is_zip64); writer.close().await.unwrap(); } /// Tests that EntryStreamWriter switches to Zip64 mode when writing too many files for a non-Zip64. #[tokio::test] async fn test_zip64_when_many_files_stream() { let mut sink = AsyncSink; let mut writer = ZipFileWriter::new(&mut sink); for i in 0..=u16::MAX as u32 + 1 { let entry = ZipEntryBuilder::new(format!("{i}").into(), Compression::Stored); let entrywriter = writer.write_entry_stream(entry).await.unwrap(); entrywriter.close().await.unwrap(); } assert!(writer.is_zip64); writer.close().await.unwrap(); } /// Tests that when force_no_zip64 is true, EntryWholeWriter errors when trying to write more than /// u16::MAX files to a single archive. #[tokio::test] async fn test_force_no_zip64_errors_with_too_many_files_whole() { let mut sink = AsyncSink; let mut writer = ZipFileWriter::new(&mut sink).force_no_zip64(); for i in 0..u16::MAX { let entry = ZipEntryBuilder::new(format!("{i}").into(), Compression::Stored); writer.write_entry_whole(entry, &[]).await.unwrap() } let entry = ZipEntryBuilder::new("65537".to_string().into(), Compression::Stored); let result = writer.write_entry_whole(entry, &[]).await; assert!(matches!(result, Err(ZipError::Zip64Needed(Zip64ErrorCase::TooManyFiles)))); } /// Tests that when force_no_zip64 is true, EntryStreamWriter errors when trying to write more than /// u16::MAX files to a single archive. #[tokio::test] async fn test_force_no_zip64_errors_with_too_many_files_stream() { let mut sink = AsyncSink; let mut writer = ZipFileWriter::new(&mut sink).force_no_zip64(); for i in 0..u16::MAX { let entry = ZipEntryBuilder::new(format!("{i}").into(), Compression::Stored); let entrywriter = writer.write_entry_stream(entry).await.unwrap(); entrywriter.close().await.unwrap(); } let entry = ZipEntryBuilder::new("65537".to_string().into(), Compression::Stored); let entrywriter = writer.write_entry_stream(entry).await.unwrap(); let result = entrywriter.close().await; assert!(matches!(result, Err(ZipError::Zip64Needed(Zip64ErrorCase::TooManyFiles)))); } /// Tests that when force_no_zip64 is true, EntryStreamWriter errors when trying to write /// a file larger than ~4 GiB to an archive. #[tokio::test] async fn test_force_no_zip64_errors_with_too_large_file_stream() { let mut sink = AsyncSink; let mut writer = ZipFileWriter::new(&mut sink).force_no_zip64(); let entry = ZipEntryBuilder::new("-".to_string().into(), Compression::Stored); let mut entrywriter = writer.write_entry_stream(entry).await.unwrap(); // Writing 4GB, 1kb at a time for _ in 0..NUM_BATCHES { entrywriter.write_all(&[0; BATCH_SIZE]).await.unwrap(); } let result = entrywriter.close().await; assert!(matches!(result, Err(ZipError::Zip64Needed(Zip64ErrorCase::LargeFile)))); } async_zip-0.0.18/src/tokio/mod.rs000064400000000000000000000033141046102023000147530ustar 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; pub mod write { //! A module which supports writing ZIP files. #[cfg(doc)] use crate::base; use tokio_util::compat::Compat; /// A [`tokio`]-specific type alias for [`base::write::ZipFileWriter`]; pub type ZipFileWriter = crate::base::write::ZipFileWriter>; /// A [`tokio`]-specific type alias for [`base::write::EntryStreamWriter`]; pub type EntryStreamWriter<'a, W> = crate::base::write::EntryStreamWriter<'a, Compat>; } async_zip-0.0.18/src/tokio/read/fs.rs000064400000000000000000000123451046102023000155230ustar 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)) } } async_zip-0.0.18/src/tokio/read/mod.rs000064400000000000000000000024331046102023000156670ustar 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>; } async_zip-0.0.18/src/utils.rs000064400000000000000000000013421046102023000142060ustar 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)), } } async_zip-0.0.18/tests/common/mod.rs000064400000000000000000000076521046102023000155020ustar 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 async_zip::base::write::ZipFileWriter; use async_zip::Compression; use async_zip::ZipEntryBuilder; use futures_lite::io::AsyncWriteExt; use tokio::fs::File; use tokio::io::BufReader; use tokio_util::compat::TokioAsyncReadCompatExt; const FOLDER_PREFIX: &str = "tests/test_inputs"; const FILE_LIST: &[&str] = &[ "sample_data/alpha/back_to_front.txt", "sample_data/alpha/front_to_back.txt", "sample_data/numeric/forward.txt", "sample_data/numeric/reverse.txt", ]; pub async fn compress_to_mem(compress: Compression) -> Vec { let mut bytes = Vec::with_capacity(10_000); let mut writer = ZipFileWriter::new(&mut bytes); for fname in FILE_LIST { let content = tokio::fs::read(format!("{FOLDER_PREFIX}/{fname}")).await.unwrap(); let opts = ZipEntryBuilder::new(fname.to_string().into(), compress); let mut entry_writer = writer.write_entry_stream(opts).await.unwrap(); entry_writer.write_all(&content).await.unwrap(); entry_writer.close().await.unwrap(); } writer.close().await.unwrap(); bytes } #[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"); } } async_zip-0.0.18/tests/compress_test.rs000064400000000000000000000055521046102023000163220ustar 00000000000000// Copyright (c) 2023 Harry [Majored] [hello@majored.pw] // MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE) use async_zip::{Compression, ZipEntryBuilder, ZipString}; use futures_lite::AsyncWriteExt; mod common; #[cfg(feature = "zstd")] #[tokio::test] async fn zip_zstd_in_out() { let zip_data = common::compress_to_mem(Compression::Zstd).await; common::check_decompress_mem(zip_data).await } #[cfg(feature = "deflate")] #[tokio::test] async fn zip_decompress_in_out() { let zip_data = common::compress_to_mem(Compression::Deflate).await; common::check_decompress_mem(zip_data).await } #[tokio::test] async fn zip_store_in_out() { let zip_data = common::compress_to_mem(Compression::Stored).await; common::check_decompress_mem(zip_data).await } #[tokio::test] async fn zip_utf8_extra_in_out_stream() { let mut zip_bytes = Vec::with_capacity(10_000); { // writing let content = "Test".as_bytes(); let mut writer = async_zip::base::write::ZipFileWriter::new(&mut zip_bytes); let filename = ZipString::new_with_alternative("\u{4E2D}\u{6587}.txt".to_string(), b"\xD6\xD0\xCe\xC4.txt".to_vec()); let opts = ZipEntryBuilder::new(filename, Compression::Stored); let mut entry_writer = writer.write_entry_stream(opts).await.unwrap(); entry_writer.write_all(content).await.unwrap(); entry_writer.close().await.unwrap(); writer.close().await.unwrap(); } { // reading let zip = async_zip::base::read::mem::ZipFileReader::new(zip_bytes).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); assert_eq!(zip_entries.len(), 1); 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())); } } #[tokio::test] async fn zip_utf8_extra_in_out_whole() { let mut zip_bytes = Vec::with_capacity(10_000); { // writing let content = "Test".as_bytes(); let mut writer = async_zip::base::write::ZipFileWriter::new(&mut zip_bytes); let filename = ZipString::new_with_alternative("\u{4E2D}\u{6587}.txt".to_string(), b"\xD6\xD0\xCe\xC4.txt".to_vec()); let opts = ZipEntryBuilder::new(filename, Compression::Stored); writer.write_entry_whole(opts, content).await.unwrap(); writer.close().await.unwrap(); } { // reading let zip = async_zip::base::read::mem::ZipFileReader::new(zip_bytes).await.unwrap(); let zip_entries: Vec<_> = zip.file().entries().to_vec(); assert_eq!(zip_entries.len(), 1); 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())); } } async_zip-0.0.18/tests/decompress_test.rs000064400000000000000000000054671046102023000166400ustar 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; const ZSTD_ZIP_FILE: &str = "tests/test_inputs/sample_data.zstd.zip"; 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 check_empty_zip_seek() { let mut data: Vec = Vec::new(); async_zip::base::write::ZipFileWriter::new(futures::io::Cursor::new(&mut data)).close().await.unwrap(); async_zip::base::read::seek::ZipFileReader::new(futures::io::Cursor::new(&data)).await.unwrap(); } #[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())); } async_zip-0.0.18/tests/test_inputs/sample_data/alpha/back_to_front.txt000064400000000000000000000006401046102023000243460ustar 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 async_zip-0.0.18/tests/test_inputs/sample_data/alpha/front_to_back.txt000064400000000000000000000006401046102023000243460ustar 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 async_zip-0.0.18/tests/test_inputs/sample_data/numeric/forward.txt000064400000000000000000000001271046102023000235550ustar 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 async_zip-0.0.18/tests/test_inputs/sample_data/numeric/reverse.txt000064400000000000000000000001271046102023000235640ustar 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 async_zip-0.0.18/tests/test_inputs/sample_data.deflate.zip000064400000000000000000000022661046102023000220430ustar 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.txtPKasync_zip-0.0.18/tests/test_inputs/sample_data.store.zip000064400000000000000000000036041046102023000215700ustar 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.txtPKvasync_zip-0.0.18/tests/test_inputs/sample_data.zstd.zip000064400000000000000000000023551046102023000214220ustar 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.txtPKasync_zip-0.0.18/tests/test_inputs/sample_data_utf8_extra.zip000064400000000000000000000003001046102023000225740ustar 00000000000000PKr W.txtup2中文.txtPKr W7 .txt YYYup2中文.txtPKm9