apt-sources-0.1.1/.cargo_vcs_info.json0000644000000001510000000000100132750ustar { "git": { "sha1": "1e006023261d2e54b9f4d8ae65a64917b381b018" }, "path_in_vcs": "apt-sources" }apt-sources-0.1.1/Cargo.lock0000644000002065030000000000100112610ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[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 = "anyhow" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "apt-sources" version = "0.1.1" dependencies = [ "deb822-fast", "indoc", "sequoia-net", "sequoia-openpgp", "tempfile", "tokio", "tracing", "url", ] [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term", ] [[package]] name = "async-trait" version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array 0.14.7", ] [[package]] name = "buffered-reader" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db26bf1f092fd5e05b5ab3be2f290915aeb6f3f20c4e9f86ce0f07f336c2412f" dependencies = [ "flate2", "libc", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", "typenum", ] [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deb822-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bf2d0fa4ce2457e94bd7efb15aeadc115297f04b660bd0da706729e0d91442" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "deb822-fast" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ea8fe9a58604b036be1759540ab0dbc8da57474a67715459653f06a467e38c" dependencies = [ "deb822-derive", ] [[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 = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ena" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-as-inner" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" 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 = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", "futures-task", "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 = "generic-array" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" dependencies = [ "typenum", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hickory-client" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "156579a5cd8d1fc6f0df87cc21b6ee870db978a163a1ba484acd98a4eff5a6de" dependencies = [ "cfg-if", "data-encoding", "futures-channel", "futures-util", "hickory-proto", "once_cell", "radix_trie", "rand", "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "hickory-proto" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", "idna", "ipnet", "once_cell", "openssl", "rand", "thiserror 1.0.69", "tinyvec", "tokio", "tracing", "url", ] [[package]] name = "hickory-resolver" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", "lru-cache", "once_cell", "parking_lot", "rand", "resolv-conf", "smallvec", "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.0", "system-configuration", "tokio", "tower-service", "tracing", "windows-registry", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "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 = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "indoc" version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "io-uring" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.10", "widestring", "windows-sys 0.48.0", "winreg", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lalrpop" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", "ena", "itertools", "lalrpop-util", "petgraph", "regex", "regex-syntax", "string_cache", "term", "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ "regex-automata", ] [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libredox" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ "linked-hash-map", ] [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memsec" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492" [[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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nibble_vec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ "smallvec", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core", "subtle", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[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.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "potential_utf" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radix_trie" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ "endian-type", "nibble_vec", ] [[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 0.2.16", ] [[package]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] [[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 = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", "mime", "native-tls", "percent-encoding", "pin-project-lite", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "resolv-conf" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.60.2", ] [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "sequoia-net" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956ef5d37e41f53259cd3c6caac5f135351ee92f76f3ac6ee9cf771ee6e33925" dependencies = [ "anyhow", "base64", "futures-util", "hickory-client", "hickory-resolver", "http", "hyper", "hyper-tls", "libc", "percent-encoding", "reqwest", "sequoia-openpgp", "thiserror 1.0.69", "tokio", "url", "z-base-32", ] [[package]] name = "sequoia-openpgp" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "015e5fc3d023418b9db98ca9a7f3e90b305872eeafe5ca45c5c32b5eb335c1e8" dependencies = [ "anyhow", "argon2", "base64", "buffered-reader", "chrono", "dyn-clone", "flate2", "getrandom 0.2.16", "idna", "lalrpop", "lalrpop-util", "libc", "memsec", "openssl", "openssl-sys", "regex", "regex-syntax", "sha1collisiondetection", "thiserror 2.0.14", "xxhash-rust", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", "ryu", "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 = "sha1collisiondetection" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "digest", "generic-array 1.2.0", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "term" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ "dirs-next", "rustversion", "winapi", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ "thiserror-impl 2.0.14", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 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.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "slab", "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "widestring" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[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.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.3", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "z-base-32" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bf7b4a78668416e1e8a332334e26fb2f377afe707f0c6feaf6ed5f9100133b" [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] apt-sources-0.1.1/Cargo.toml0000644000000036740000000000100113100ustar # 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 = "apt-sources" version = "0.1.1" authors = [ "Michał Fita <4925040+michalfita@users.noreply.github.com>", "Jelmer Vernooij ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A parser for APT source files (package repositories specification)" homepage = "https://github.com/jelmer/deb822-lossless" readme = false keywords = [ "debian", "deb822", "rfc822", "edit", "apt", ] categories = ["parser-implementations"] license = "Apache-2.0" repository = "https://github.com/jelmer/deb822-lossless" [features] default = [] key-management = [ "dep:sequoia-openpgp", "dep:sequoia-net", "dep:tokio", ] tracing = ["dep:tracing"] [lib] name = "apt_sources" path = "src/lib.rs" [[example]] name = "source-for-docker" path = "examples/source-for-docker.rs" [dependencies.deb822-fast] version = ">=0.1" features = ["derive"] [dependencies.sequoia-net] version = "0.30" optional = true default-features = false [dependencies.sequoia-openpgp] version = "2.0" features = [ "compression-deflate", "crypto-openssl", ] optional = true default-features = false [dependencies.tokio] version = "1" features = [ "rt", "macros", ] optional = true [dependencies.tracing] version = "0.1" optional = true [dependencies.url] version = "2.5.4" features = ["serde"] [dev-dependencies.indoc] version = "2.0.6" [dev-dependencies.tempfile] version = "3" apt-sources-0.1.1/Cargo.toml.orig000064400000000000000000000021351046102023000147600ustar 00000000000000[package] name = "apt-sources" authors = ["Michał Fita <4925040+michalfita@users.noreply.github.com>", "Jelmer Vernooij "] edition = "2021" version = "0.1.1" license = "Apache-2.0" description = "A parser for APT source files (package repositories specification)" repository = { workspace = true } homepage = { workspace = true } keywords = ["debian", "deb822", "rfc822", "edit", "apt"] categories = ["parser-implementations"] [features] default = [] key-management = ["dep:sequoia-openpgp", "dep:sequoia-net", "dep:tokio"] tracing = ["dep:tracing"] [dependencies] deb822-fast = { version = ">=0.1", path = "../deb822-fast", features = ["derive"] } url = { version = "2.5.4", features = ["serde"] } tracing = { version = "0.1", optional = true } sequoia-net = { version = "0.30", default-features = false, optional = true } tokio = { version = "1", features = ["rt", "macros"], optional = true } sequoia-openpgp = { version = "2.0", default-features = false, features = ["compression-deflate", "crypto-openssl"], optional = true } [dev-dependencies] indoc = { version = "2.0.6" } tempfile = "3" apt-sources-0.1.1/examples/source-for-docker.rs000064400000000000000000000006401046102023000176050ustar 00000000000000use apt_sources::Repositories; use indoc::indoc; pub const TEXT: &str = indoc! {r#" Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: noble Components: stable Architectures: amd64 Signed-By: /usr/share/keyrings/docker.gpg "#}; pub fn main() { let repos = TEXT.parse::().unwrap(); let suites = repos[0].suites(); println!("{}", suites.join(" ")); } apt-sources-0.1.1/src/distribution.rs000064400000000000000000000201401046102023000157410ustar 00000000000000use std::fs; use std::process::Command; /// Represents a Linux distribution #[derive(Debug, Clone, PartialEq)] pub enum Distribution { /// Ubuntu Linux Ubuntu, /// Debian Linux Debian, /// Other distribution Other(String), } impl Distribution { /// Get the current system's distribution information pub fn current() -> Result { // First try lsb_release for distribution ID let lsb_id = Command::new("lsb_release").args(["-i", "-s"]).output(); if let Ok(output) = lsb_id { if output.status.success() { let distro_id = String::from_utf8_lossy(&output.stdout) .trim() .to_lowercase(); return Ok(match distro_id.as_str() { "ubuntu" => Distribution::Ubuntu, "debian" => Distribution::Debian, other => Distribution::Other(other.to_owned()), }); } } // Fall back to /etc/os-release if lsb_release fails if let Ok(content) = fs::read_to_string("/etc/os-release") { for line in content.lines() { if line.starts_with("ID=") { let id = line .trim_start_matches("ID=") .trim_matches('"') .to_lowercase(); return Ok(match id.as_str() { "ubuntu" => Distribution::Ubuntu, "debian" => Distribution::Debian, other => Distribution::Other(other.to_owned()), }); } } } // If all else fails, assume Debian-based Ok(Distribution::Other("unknown".to_owned())) } /// Get default components for this distribution pub fn default_components(&self) -> Vec<&'static str> { match self { Distribution::Ubuntu => vec!["main", "universe"], Distribution::Debian => vec!["main"], Distribution::Other(_) => vec!["main"], } } /// Check if a repository is a main distribution repository pub fn is_main_repository(&self, repo: &crate::Repository) -> bool { for uri in &repo.uris { if let Some(host) = uri.host_str() { match self { Distribution::Ubuntu => { if host.contains("ubuntu.com") || host.contains("canonical.com") || host == "archive.ubuntu.com" || host == "security.ubuntu.com" || host == "ports.ubuntu.com" { return true; } } Distribution::Debian => { if host.contains("debian.org") || host == "deb.debian.org" || host == "security.debian.org" { return true; } } _ => {} } } } false } } /// Get system information (codename and architecture) pub fn get_system_info() -> Result<(String, String), String> { // Get distribution codename let lsb_release = Command::new("lsb_release") .args(["-c", "-s"]) .output() .map_err(|e| format!("Failed to run lsb_release: {}", e))?; if !lsb_release.status.success() { return Err("Failed to determine distribution codename".to_string()); } let codename = String::from_utf8_lossy(&lsb_release.stdout) .trim() .to_string(); // Get architecture let dpkg_arch = Command::new("dpkg") .arg("--print-architecture") .output() .map_err(|e| format!("Failed to run dpkg: {}", e))?; if !dpkg_arch.status.success() { return Err("Failed to determine system architecture".to_string()); } let arch = String::from_utf8_lossy(&dpkg_arch.stdout) .trim() .to_string(); Ok((codename, arch)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_is_main_repository() { let dist = Distribution::Ubuntu; let repo = crate::Repository { uris: vec![url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap()], suites: vec!["jammy".to_string()], components: Some(vec!["main".to_string()]), ..Default::default() }; assert!(dist.is_main_repository(&repo)); let non_main_repo = crate::Repository { uris: vec![url::Url::parse("http://example.com/ubuntu").unwrap()], suites: vec!["jammy".to_string()], components: Some(vec!["main".to_string()]), ..Default::default() }; assert!(!dist.is_main_repository(&non_main_repo)); } #[test] fn test_is_main_repository_all_ubuntu_hosts() { let dist = Distribution::Ubuntu; // Test each Ubuntu host individually let ubuntu_hosts = [ "http://archive.ubuntu.com/ubuntu", "http://security.ubuntu.com/ubuntu", "http://ports.ubuntu.com/ubuntu-ports", "http://us.archive.ubuntu.com/ubuntu", // contains ubuntu.com "http://mirrors.canonical.com/ubuntu", // contains canonical.com ]; for host in &ubuntu_hosts { let repo = crate::Repository { uris: vec![url::Url::parse(host).unwrap()], ..Default::default() }; assert!(dist.is_main_repository(&repo), "Failed for host: {}", host); } } #[test] fn test_is_main_repository_all_debian_hosts() { let dist = Distribution::Debian; // Test each Debian host individually let debian_hosts = [ "http://deb.debian.org/debian", "http://security.debian.org/debian-security", "http://ftp.debian.org/debian", // contains debian.org "http://mirrors.debian.org/debian", // contains debian.org ]; for host in &debian_hosts { let repo = crate::Repository { uris: vec![url::Url::parse(host).unwrap()], ..Default::default() }; assert!(dist.is_main_repository(&repo), "Failed for host: {}", host); } } #[test] fn test_is_main_repository_other_distribution() { let dist = Distribution::Other("mint".to_string()); // Other distributions should not match any repository let repo = crate::Repository { uris: vec![url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap()], ..Default::default() }; assert!(!dist.is_main_repository(&repo)); let repo2 = crate::Repository { uris: vec![url::Url::parse("http://deb.debian.org/debian").unwrap()], ..Default::default() }; assert!(!dist.is_main_repository(&repo2)); } #[test] fn test_is_main_repository_empty_uris() { let dist = Distribution::Ubuntu; let repo = crate::Repository { uris: vec![], ..Default::default() }; assert!(!dist.is_main_repository(&repo)); } #[test] fn test_is_main_repository_multiple_uris() { let dist = Distribution::Ubuntu; let repo = crate::Repository { uris: vec![ url::Url::parse("http://example.com/ubuntu").unwrap(), url::Url::parse("http://archive.ubuntu.com/ubuntu").unwrap(), ], ..Default::default() }; // Should return true if ANY URI matches assert!(dist.is_main_repository(&repo)); } #[test] fn test_default_components() { assert_eq!( Distribution::Ubuntu.default_components(), vec!["main", "universe"] ); assert_eq!(Distribution::Debian.default_components(), vec!["main"]); assert_eq!( Distribution::Other("mint".to_string()).default_components(), vec!["main"] ); } } apt-sources-0.1.1/src/error.rs000064400000000000000000000123371046102023000143640ustar 00000000000000//! A module for handling errors in `apt-sources` crate of `deb822-rs` project. //! It intends to address error handling in meaningful manner, less vague than just passing //! `String` as error. /// Errors for APT sources parsing and conversion to `Repository` #[derive(Debug)] pub enum RepositoryError { /// Invalid repository format InvalidFormat, /// Invalid repository URI InvalidUri, /// Missing repository URI - mandatory MissingUri, /// Unrecognized repository type InvalidType, /// The `Signed-By` field is incorrect InvalidSignature, /// Errors in lossy serializer or deserializer Lossy(deb822_fast::Error), /// I/O Error Io(std::io::Error), } /// Errors that can occur when loading repositories from directories #[derive(Debug)] pub enum LoadError { /// Failed to read a file Io { /// The path that failed to be read path: std::path::PathBuf, /// The underlying I/O error error: std::io::Error, }, /// Failed to parse a file Parse { /// The path that failed to be parsed path: std::path::PathBuf, /// The parsing error message error: String, }, /// Failed to read directory entries DirectoryRead { /// The directory path that failed to be read path: std::path::PathBuf, /// The underlying I/O error error: std::io::Error, }, } impl From for RepositoryError { fn from(e: std::io::Error) -> Self { Self::Io(e) } } impl std::fmt::Display for RepositoryError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Self::InvalidFormat => write!(f, "Invalid repository format"), Self::InvalidUri => write!(f, "Invalid repository URI"), Self::MissingUri => write!(f, "Missing repository URI"), Self::InvalidType => write!(f, "Invalid repository type"), Self::InvalidSignature => write!(f, "The field `Signed-By` is incorrect"), Self::Lossy(e) => write!(f, "Lossy parser error: {}", e), Self::Io(e) => write!(f, "IO error: {}", e), } } } impl std::fmt::Display for LoadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Self::Io { path, error } => write!(f, "Failed to read {}: {}", path.display(), error), Self::Parse { path, error } => { write!(f, "Failed to parse {}: {}", path.display(), error) } Self::DirectoryRead { path, error } => { write!(f, "Failed to read directory {}: {}", path.display(), error) } } } } impl std::error::Error for RepositoryError {} impl std::error::Error for LoadError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_repository_error_display() { // Test each error variant assert_eq!( RepositoryError::InvalidFormat.to_string(), "Invalid repository format" ); assert_eq!( RepositoryError::InvalidUri.to_string(), "Invalid repository URI" ); assert_eq!( RepositoryError::MissingUri.to_string(), "Missing repository URI" ); assert_eq!( RepositoryError::InvalidType.to_string(), "Invalid repository type" ); assert_eq!( RepositoryError::InvalidSignature.to_string(), "The field `Signed-By` is incorrect" ); // Test lossy error let lossy_err = deb822_fast::Error::UnexpectedEof; let repo_err = RepositoryError::Lossy(lossy_err); assert!(repo_err.to_string().contains("Lossy parser error:")); // Test IO error let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); let repo_err = RepositoryError::from(io_err); assert!(repo_err.to_string().contains("IO error:")); } #[test] fn test_load_error_display() { use std::path::PathBuf; // Test IO error let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); let load_err = LoadError::Io { path: PathBuf::from("/test/path"), error: io_err, }; assert!(load_err.to_string().contains("Failed to read /test/path")); assert!(load_err.to_string().contains("file not found")); // Test Parse error let parse_err = LoadError::Parse { path: PathBuf::from("/test/file.list"), error: "Invalid format".to_string(), }; assert!(parse_err .to_string() .contains("Failed to parse /test/file.list")); assert!(parse_err.to_string().contains("Invalid format")); // Test DirectoryRead error let dir_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied"); let load_err = LoadError::DirectoryRead { path: PathBuf::from("/test/dir"), error: dir_err, }; assert!(load_err .to_string() .contains("Failed to read directory /test/dir")); assert!(load_err.to_string().contains("access denied")); } } apt-sources-0.1.1/src/key_management.rs000064400000000000000000000154431046102023000162200ustar 00000000000000use crate::signature::Signature; use sequoia_openpgp::cert::CertParser; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::Cert; use std::fmt; use std::time::SystemTime; /// Errors that can occur during key management operations #[derive(Debug, Clone, PartialEq, Eq)] pub enum KeyManagementError { /// Failed to parse key data ParseError(String), /// No valid certificates found in key data NoCertificates, /// Key has expired KeyExpired, /// Key is not yet valid or has expired KeyNotValid, /// Key expires soon (includes days until expiration) KeyExpiringSoon(u64), /// Fingerprint mismatch FingerprintMismatch { /// Expected fingerprint expected: String, /// Actual fingerprint found actual: String, }, /// Invalid fingerprint format InvalidFingerprint(String), /// Invalid fingerprint length InvalidFingerprintLength { /// Actual length of the fingerprint length: usize, /// Expected length description expected: String, }, } impl fmt::Display for KeyManagementError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { KeyManagementError::ParseError(msg) => write!(f, "Failed to parse key data: {}", msg), KeyManagementError::NoCertificates => { write!(f, "No valid certificates found in key data") } KeyManagementError::KeyExpired => write!(f, "Key has expired"), KeyManagementError::KeyNotValid => write!(f, "Key has expired or is not yet valid"), KeyManagementError::KeyExpiringSoon(days) => write!(f, "Key expires in {} days", days), KeyManagementError::FingerprintMismatch { expected, actual } => { write!( f, "Fingerprint mismatch! Expected: {}, Got: {}", expected, actual ) } KeyManagementError::InvalidFingerprint(msg) => write!(f, "{}", msg), KeyManagementError::InvalidFingerprintLength { length, expected } => { write!( f, "Invalid fingerprint length: {}. Expected {}", length, expected ) } } } } impl std::error::Error for KeyManagementError {} /// Check if a GPG key is expired or about to expire pub fn check_key_expiration(cert: &Cert) -> Option { use sequoia_openpgp::policy::StandardPolicy; let policy = StandardPolicy::new(); let now = SystemTime::now(); // Check if the certificate is alive according to the policy match cert.with_policy(&policy, now) { Ok(valid_cert) => { // Check if primary key has expiration if let Some(expiration) = valid_cert.primary_key().key_expiration_time() { if expiration <= now { return Some(KeyManagementError::KeyExpired); } // Warn if expiring within 30 days if let Ok(duration_until) = expiration.duration_since(now) { let days_until = duration_until.as_secs() / 86400; if days_until < 30 { return Some(KeyManagementError::KeyExpiringSoon(days_until)); } } } None } Err(_) => { // Certificate or one of its components has expired or is not valid Some(KeyManagementError::KeyNotValid) } } } /// Verify that a key's fingerprint matches the expected fingerprint pub fn verify_key_fingerprint( key_data: &str, expected_fingerprint: &str, ) -> Result<(), KeyManagementError> { let parser = CertParser::from_bytes(key_data.as_bytes()) .map_err(|e| KeyManagementError::ParseError(e.to_string()))?; let certs: Result, _> = parser.collect(); let certs = certs.map_err(|e| KeyManagementError::ParseError(e.to_string()))?; if certs.is_empty() { return Err(KeyManagementError::NoCertificates); } let cert = &certs[0]; let fingerprint = cert.fingerprint().to_hex(); // Normalize fingerprints for comparison (remove spaces and make uppercase) let normalized_actual = fingerprint.replace(" ", "").to_uppercase(); let normalized_expected = expected_fingerprint.replace(" ", "").to_uppercase(); if normalized_actual != normalized_expected { return Err(KeyManagementError::FingerprintMismatch { expected: expected_fingerprint.to_string(), actual: fingerprint, }); } Ok(()) } /// Create an inline signature from GPG key data pub fn create_inline_signature(key_data: &str) -> Result { // First, let's parse the key to validate it let parser = CertParser::from_bytes(key_data.as_bytes()) .map_err(|e| KeyManagementError::ParseError(e.to_string()))?; let certs: Result, _> = parser.collect(); let certs = certs.map_err(|e| KeyManagementError::ParseError(e.to_string()))?; if certs.is_empty() { return Err(KeyManagementError::NoCertificates); } // Check for expiration if let Some(error) = check_key_expiration(&certs[0]) { eprintln!("Warning: {}", error); } // Create inline signature with the key data (using KeyBlock variant) Ok(Signature::KeyBlock(key_data.to_string())) } /// Validate a GPG fingerprint format pub fn validate_fingerprint(fingerprint: &str) -> Result { // Remove spaces and convert to uppercase let cleaned = fingerprint.replace(" ", "").to_uppercase(); // Check if it's a valid hex string if !cleaned.chars().all(|c| c.is_ascii_hexdigit()) { return Err(KeyManagementError::InvalidFingerprint( "Fingerprint must contain only hexadecimal characters".to_string(), )); } // GPG fingerprints should be 40 characters (160 bits) for SHA-1 // or 64 characters (256 bits) for SHA-256 if cleaned.len() != 40 && cleaned.len() != 64 { return Err(KeyManagementError::InvalidFingerprintLength { length: cleaned.len(), expected: "40 or 64 characters".to_string(), }); } Ok(cleaned) } #[cfg(test)] mod tests { use super::*; #[test] fn test_validate_fingerprint() { // Valid SHA-1 fingerprint assert!(validate_fingerprint("1234567890ABCDEF1234567890ABCDEF12345678").is_ok()); // Valid with spaces assert!(validate_fingerprint("1234 5678 90AB CDEF 1234 5678 90AB CDEF 1234 5678").is_ok()); // Invalid characters assert!(validate_fingerprint("XXXX567890ABCDEF1234567890ABCDEF12345678").is_err()); // Invalid length assert!(validate_fingerprint("1234567890ABCDEF").is_err()); } } apt-sources-0.1.1/src/keyserver.rs000064400000000000000000000246751046102023000152620ustar 00000000000000//! Keyserver operations for downloading and verifying GPG keys //! //! This module provides functionality for interacting with keyservers using Sequoia. //! It requires the `key-management` feature to be enabled. #![cfg(feature = "key-management")] use sequoia_net::KeyServer; use sequoia_openpgp::{parse::Parse, serialize::Marshal, Cert, KeyHandle}; /// Download a GPG key from a keyserver /// /// # Arguments /// * `fingerprint` - The fingerprint of the key to download (hex format) /// * `keyserver_url` - The URL of the keyserver (e.g., "hkps://keys.openpgp.org") /// /// # Returns /// The key in ASCII-armored format pub async fn download_key_from_keyserver( fingerprint: &str, keyserver_url: &str, ) -> Result { // Create a KeyHandle from the fingerprint let key_handle: KeyHandle = fingerprint .parse() .map_err(|e| format!("Invalid fingerprint format: {}", e))?; // Create a keyserver client let keyserver = KeyServer::new(keyserver_url) .map_err(|e| format!("Failed to create keyserver client: {}", e))?; // Download the certificates (keyserver may return multiple) let certs = keyserver .get(key_handle) .await .map_err(|e| format!("Failed to download key from keyserver: {}", e))?; // Get the first certificate let cert = certs .into_iter() .next() .ok_or_else(|| "No certificates found for the given fingerprint".to_string())? .map_err(|e| format!("Failed to parse certificate: {}", e))?; // Serialize the certificate to armored format let mut armored = Vec::new(); cert.armored() .serialize(&mut armored) .map_err(|e| format!("Failed to serialize key: {}", e))?; String::from_utf8(armored).map_err(|e| format!("Failed to convert key to string: {}", e)) } /// Download a GPG key from a keyserver (synchronous wrapper) /// /// This is a convenience function for synchronous code that internally /// creates a Tokio runtime to execute the async operation. /// /// # Arguments /// * `fingerprint` - The fingerprint of the key to download (hex format) /// * `keyserver_url` - The URL of the keyserver (e.g., "hkps://keys.openpgp.org") /// /// # Returns /// The key in ASCII-armored format pub fn download_key_from_keyserver_sync( fingerprint: &str, keyserver_url: &str, ) -> Result { // Check if we're already in a tokio runtime if let Ok(handle) = tokio::runtime::Handle::try_current() { // We're in a runtime, use block_in_place tokio::task::block_in_place(|| { handle.block_on(download_key_from_keyserver(fingerprint, keyserver_url)) }) } else { // No runtime, create one let runtime = tokio::runtime::Runtime::new() .map_err(|e| format!("Failed to create async runtime: {}", e))?; runtime.block_on(download_key_from_keyserver(fingerprint, keyserver_url)) } } /// Verify that a key has the expected fingerprint /// /// # Arguments /// * `key_data` - The key data in ASCII-armored or binary format /// * `expected_fingerprint` - The expected fingerprint (spaces and case are ignored) /// /// # Returns /// Ok(()) if the fingerprint matches, Err with details otherwise pub fn verify_key_fingerprint(key_data: &str, expected_fingerprint: &str) -> Result<(), String> { // Parse the certificate from the armored data let cert = Cert::from_bytes(key_data.as_bytes()).map_err(|e| format!("Failed to parse key: {}", e))?; // Get the fingerprint of the primary key let actual_fingerprint = cert.fingerprint().to_hex(); // Normalize both fingerprints for comparison (remove spaces, convert to uppercase) let normalize = |fp: &str| -> String { fp.chars() .filter(|c| c.is_alphanumeric()) .collect::() .to_uppercase() }; let expected_normalized = normalize(expected_fingerprint); let actual_normalized = normalize(&actual_fingerprint); // Handle both short (16 char) and long (40 char) fingerprints if expected_normalized.len() == 16 { // Short fingerprint - compare with the last 16 chars of actual if actual_normalized.len() >= 16 && expected_normalized != &actual_normalized[actual_normalized.len() - 16..] { return Err(format!( "Fingerprint mismatch: expected {}, got {}", expected_fingerprint, actual_fingerprint )); } } else if expected_normalized != actual_normalized { return Err(format!( "Fingerprint mismatch: expected {}, got {}", expected_fingerprint, actual_fingerprint )); } Ok(()) } /// Parse a key and extract its metadata /// /// # Arguments /// * `key_data` - The key data in ASCII-armored or binary format /// /// # Returns /// A tuple of (fingerprint, user_ids) where user_ids is a vector of email addresses pub fn parse_key_metadata(key_data: &str) -> Result<(String, Vec), String> { // Parse the certificate let cert = Cert::from_bytes(key_data.as_bytes()).map_err(|e| format!("Failed to parse key: {}", e))?; // Get the fingerprint let fingerprint = cert.fingerprint().to_hex(); // Extract user IDs (email addresses) let user_ids: Vec = cert .userids() .filter_map(|uid| { uid.userid() .email() .expect("Failed to get email") .map(String::from) }) .collect(); Ok((fingerprint, user_ids)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_normalize_fingerprint() { let normalize = |fp: &str| -> String { fp.chars() .filter(|c| c.is_alphanumeric()) .collect::() .to_uppercase() }; assert_eq!(normalize("1234 5678 90AB CDEF"), "1234567890ABCDEF"); assert_eq!(normalize("1234567890abcdef"), "1234567890ABCDEF"); assert_eq!(normalize("12:34:56:78:90:AB:CD:EF"), "1234567890ABCDEF"); assert_eq!(normalize("0x1234567890ABCDEF"), "0X1234567890ABCDEF"); } #[test] fn test_verify_fingerprint_matching() { // Use a real test key - this is a minimal valid OpenPGP key let key_data = r#"-----BEGIN PGP PUBLIC KEY BLOCK----- mDMEZIYC9xYJKwYBBAHaRw8BAQdAz5feTnR7DwGfLHLkBhoHu6GTFprNle/n/Iup fTUT6Z60BlRlc3QgMYiZBBMWCgBBFiEE7t0zdTa4BwHfZTj0iL0eQBFT2xYFAmSG AvcCGwMFCQPCZwAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQiL0eQBFT 2xYkEgD/b3p0QehuzJiuJLijVKOB7WKnLbnt2g8cbW7EARDHkWYBANREqydl1OYJ c7B8N9l1cG2TCem0K3SXD8p1ELDs2aEJuDgEZIYC9xIKKwYBBAGXVQEFAQEHQOrf 4RAemEw5X5MBceW1BpYtKp+jH5ypaxpILGz7OVIfAwEIB4h+BBgWCgAmFiEE7t0z dTa4BwHfZTj0iL0eQBFT2xYFAmSGAvcCGwwFCQPCZwAACgkQiL0eQBFT2xbL8gEA 2NenoDwxr8aWnlhajSJZz8UYNkzJNJQCPG2cukPNf3YA/RYhzCxJMkMYJ3DXtiUh UqZBMYWFftpFkh5E5FGqs7kO =Rt6r -----END PGP PUBLIC KEY BLOCK-----"#; // The actual fingerprint of this test key (as parsed by Sequoia) let fingerprint = "7607A01349161EA59DC551A654F610003149BA6E"; // Note: This test uses a dummy fingerprint that won't match the actual key // The actual fingerprint would need to be determined by parsing the key // Test with spaces let fingerprint_with_spaces = fingerprint .chars() .enumerate() .map(|(i, c)| { if i > 0 && i % 4 == 0 { format!(" {}", c) } else { c.to_string() } }) .collect::(); assert!(verify_key_fingerprint(key_data, &fingerprint_with_spaces).is_ok()); // Test lowercase assert!(verify_key_fingerprint(key_data, &fingerprint.to_lowercase()).is_ok()); // Test short fingerprint (last 16 chars) assert!(verify_key_fingerprint(key_data, &fingerprint[fingerprint.len() - 16..]).is_ok()); // Test wrong fingerprint assert!( verify_key_fingerprint(key_data, "0000000000000000000000000000000000000000").is_err() ); } #[test] fn test_parse_key_metadata() { // Use the same valid test key let key_data = r#"-----BEGIN PGP PUBLIC KEY BLOCK----- mDMEZIYC9xYJKwYBBAHaRw8BAQdAz5feTnR7DwGfLHLkBhoHu6GTFprNle/n/Iup fTUT6Z60BlRlc3QgMYiZBBMWCgBBFiEE7t0zdTa4BwHfZTj0iL0eQBFT2xYFAmSG AvcCGwMFCQPCZwAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQiL0eQBFT 2xYkEgD/b3p0QehuzJiuJLijVKOB7WKnLbnt2g8cbW7EARDHkWYBANREqydl1OYJ c7B8N9l1cG2TCem0K3SXD8p1ELDs2aEJuDgEZIYC9xIKKwYBBAGXVQEFAQEHQOrf 4RAemEw5X5MBceW1BpYtKp+jH5ypaxpILGz7OVIfAwEIB4h+BBgWCgAmFiEE7t0z dTa4BwHfZTj0iL0eQBFT2xYFAmSGAvcCGwwFCQPCZwAACgkQiL0eQBFT2xbL8gEA 2NenoDwxr8aWnlhajSJZz8UYNkzJNJQCPG2cukPNf3YA/RYhzCxJMkMYJ3DXtiUh UqZBMYWFftpFkh5E5FGqs7kO =Rt6r -----END PGP PUBLIC KEY BLOCK-----"#; let result = parse_key_metadata(key_data); assert!(result.is_ok()); let (fingerprint, user_ids) = result.unwrap(); assert_eq!( fingerprint.to_uppercase(), "7607A01349161EA59DC551A654F610003149BA6E" ); // This test key has "Test 1" as user ID but no email, so user_ids should be empty assert!(user_ids.is_empty()); } #[test] fn test_parse_key_with_email() { // A test key with an email address let key_data = r#"-----BEGIN PGP PUBLIC KEY BLOCK----- mDMEZqWp9BYJKwYBBAHaRw8BAQdAsHf0MhUvIVpSFsEZvQnnF3IXw2lODfCU8naR U4juKjW0IVRlc3QgVXNlciA8dGVzdC51c2VyQGV4YW1wbGUuY29tPoiZBBMWCgBB FiEEJ0o1v8rRKmkqCwVhzaW9vsKi7GAFAmalqfQCGwMFCQPCZwAFCwkIBwICIgIG FQoJCAsCBBYCAwECHgcCF4AACgkQzaW9vsKi7GA8xQD/YSHd7Wrf7RG4dNQJvbol GMQX3J9XQFQsZhJzvF2PJQkA/A1MHSaoFIHPQ8nKMBje2WLMNan8vPJjVoGVOoUg 4Y0GuDgEZqWp9BIKKwYBBAGXVQEFAQEHQPOXyfn9OI/Ge8rqMAYiJJSKlbhHNuv6 7s9VhtKrJbclAwEIB4h+BBgWCgAmFiEEJ0o1v8rRKmkqCwVhzaW9vsKi7GAFAmal qfQCGwwFCQPCZwAACgkQzaW9vsKi7GCGegD8CzKOL6csQ6xRGBBb7Q5P0GlJHF4v s7jNLfTdgJ4AKEEA/i8Hj1Q4KmgyqE8lpZmqfAdof/LHDlLg4E5Ry/4CgIUN =zUh6 -----END PGP PUBLIC KEY BLOCK-----"#; let result = parse_key_metadata(key_data); assert!(result.is_ok()); let (fingerprint, user_ids) = result.unwrap(); assert_eq!( fingerprint.to_uppercase(), "7AC6142889FA53D9268C3278280EB318F8281840" ); assert_eq!(user_ids, vec!["test.user@example.com"]); } #[test] fn test_invalid_key_data() { let invalid_data = "This is not a PGP key"; assert!(verify_key_fingerprint(invalid_data, "any_fingerprint").is_err()); assert!(parse_key_metadata(invalid_data).is_err()); } } apt-sources-0.1.1/src/lib.rs000064400000000000000000001057521046102023000140050ustar 00000000000000#![deny(missing_docs)] //! A library for parsing and manipulating APT source files that //! use the DEB822 format to hold package repositories specifications. //! //!
//! //! Currently only lossy _serialization_ is implemented, lossless support //! retaining file sequence and comments would come at later date. //! //!
//! //! # Examples //! //! ```rust //! //! use apt_sources::Repositories; //! use std::path::Path; //! //! let text = r#"Types: deb //! URIs: http://ports.ubuntu.com/ //! Suites: noble //! Components: stable //! Architectures: arm64 //! Signed-By: //! -----BEGIN PGP PUBLIC KEY BLOCK----- //! . //! mDMEY865UxYJKwYBBAHaRw8BAQdAd7Z0srwuhlB6JKFkcf4HU4SSS/xcRfwEQWzr //! crf6AEq0SURlYmlhbiBTdGFibGUgUmVsZWFzZSBLZXkgKDEyL2Jvb2t3b3JtKSA8 //! ZGViaWFuLXJlbGVhc2VAbGlzdHMuZGViaWFuLm9yZz6IlgQTFggAPhYhBE1k/sEZ //! wgKQZ9bnkfjSWFuHg9SBBQJjzrlTAhsDBQkPCZwABQsJCAcCBhUKCQgLAgQWAgMB //! Ah4BAheAAAoJEPjSWFuHg9SBSgwBAP9qpeO5z1s5m4D4z3TcqDo1wez6DNya27QW //! WoG/4oBsAQCEN8Z00DXagPHbwrvsY2t9BCsT+PgnSn9biobwX7bDDg== //! =5NZE //! -----END PGP PUBLIC KEY BLOCK-----"#; //! //! let r = text.parse::().unwrap(); //! let suites = r[0].suites(); //! assert_eq!(suites[0], "noble"); //! ``` //! // TODO: Not supported yet: // See the ``lossless`` module (behind the ``lossless`` feature) for a more forgiving parser that // allows partial parsing, parsing files with errors and unknown fields and editing while // preserving formatting. use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822, ToDeb822Paragraph}; use error::{LoadError, RepositoryError}; use signature::Signature; use std::path::Path; use std::result::Result; use std::{collections::HashSet, ops::Deref, str::FromStr}; use url::Url; /// Distribution detection and utilities pub mod distribution; pub mod error; #[cfg(feature = "key-management")] /// Key management utilities for GPG keys pub mod key_management; #[cfg(feature = "key-management")] pub mod keyserver; pub mod ppa; pub mod signature; /// Module for managing APT source lists pub mod sources_manager; /// A representation of the repository type, by role of packages it can provide, either `Binary` /// (indicated by `deb`) or `Source` (indicated by `deb-src`). #[derive(PartialEq, Eq, Hash, Debug, Clone)] pub enum RepositoryType { /// Repository with binary packages, indicated as `deb` Binary, /// Repository with source packages, indicated as `deb-src` Source, } impl FromStr for RepositoryType { type Err = RepositoryError; fn from_str(s: &str) -> Result { match s { "deb" => Ok(RepositoryType::Binary), "deb-src" => Ok(RepositoryType::Source), _ => Err(RepositoryError::InvalidType), } } } impl From<&RepositoryType> for String { fn from(value: &RepositoryType) -> Self { match value { RepositoryType::Binary => "deb".to_owned(), RepositoryType::Source => "deb-src".to_owned(), } } } impl std::fmt::Display for RepositoryType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { RepositoryType::Binary => "deb", RepositoryType::Source => "deb-src", }; write!(f, "{}", s) } } #[derive(Debug, Clone, PartialEq)] /// Enumeration for fields like `By-Hash` which have third value of `force` pub enum YesNoForce { /// True Yes, /// False No, /// Forced Force, } impl FromStr for YesNoForce { type Err = RepositoryError; fn from_str(s: &str) -> Result { match s { "yes" => Ok(Self::Yes), "no" => Ok(Self::No), "force" => Ok(Self::Force), _ => Err(RepositoryError::InvalidType), } } } impl From<&YesNoForce> for String { fn from(value: &YesNoForce) -> Self { match value { YesNoForce::Yes => "yes".to_owned(), YesNoForce::No => "no".to_owned(), YesNoForce::Force => "force".to_owned(), } } } impl std::fmt::Display for YesNoForce { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { YesNoForce::Yes => "yes", YesNoForce::No => "no", YesNoForce::Force => "force", }; write!(f, "{}", s) } } fn deserialize_types(text: &str) -> Result, RepositoryError> { text.split_whitespace() .map(RepositoryType::from_str) .collect::, RepositoryError>>() } fn serialize_types(files: &HashSet) -> String { use std::fmt::Write; let mut result = String::new(); for (i, rt) in files.iter().enumerate() { if i > 0 { result.push('\n'); } write!(&mut result, "{}", rt).unwrap(); } result } fn deserialize_uris(text: &str) -> Result, String> { // TODO: bad error type text.split_whitespace() .map(Url::from_str) .collect::, _>>() .map_err(|e| e.to_string()) // TODO: bad error type } fn serialize_uris(uris: &[Url]) -> String { let mut result = String::new(); for (i, uri) in uris.iter().enumerate() { if i > 0 { result.push(' '); } result.push_str(uri.as_str()); } result } fn deserialize_string_chain(text: &str) -> Result, String> { // TODO: bad error type Ok(text.split_whitespace().map(|x| x.to_string()).collect()) } fn deserialize_yesno(text: &str) -> Result { // TODO: bad error type match text { "yes" => Ok(true), "no" => Ok(false), _ => Err("Invalid value for yes/no field".to_owned()), } } fn serializer_yesno(value: &bool) -> String { if *value { "yes".to_string() } else { "no".to_string() } } fn serialize_string_chain(chain: &[String]) -> String { chain.join(" ") } /// A structure representing APT repository as declared by DEB822 source file /// /// According to `sources.list(5)` man pages, only four fields are mandatory: /// * `Types` either `deb` or/and `deb-src` /// * `URIs` to repositories holding valid APT structure (unclear if multiple are allowed) /// * `Suites` usually being distribution codenames /// * `Component` most of the time `main`, but it's a section of the repository /// /// The manpage specifies following optional fields /// * `Enabled` is a yes/no field, default yes /// * `Architectures` /// * `Languages` /// * `Targets` /// * `PDiffs` is a yes/no field /// * `By-Hash` is a yes/no/force field /// * `Allow-Insecure` is a yes/no field, default no /// * `Allow-Weak` is a yes/no field, default no /// * `Allow-Downgrade-To-Insecure` is a yes/no field, default no /// * `Trusted` us a yes/no field /// * `Signed-By` is either path to the key or PGP key block /// * `Check-Valid-Until` is a yes/no field /// * `Valid-Until-Min` /// * `Valid-Until-Max` /// * `Check-Date` is a yes/no field /// * `Date-Max-Future` /// * `InRelease-Path` relative path /// * `Snapshot` either `enable` or a snapshot ID /// /// The unit tests of APT use: /// * `Description` /// /// The RepoLib tool uses: /// * `X-Repolib-Name` identifier for own reference, meaningless for APT /// /// Note: Multivalues `*-Add` & `*-Remove` semantics aren't supported. #[derive(FromDeb822, ToDeb822, Clone, PartialEq, /*Eq,*/ Debug, Default)] pub struct Repository { /// If `no` (false) the repository is ignored by APT #[deb822(field = "Enabled", deserialize_with = deserialize_yesno, serialize_with = serializer_yesno)] // TODO: support for `default` if omitted is missing pub enabled: Option, /// The value `RepositoryType::Binary` (`deb`) or/and `RepositoryType::Source` (`deb-src`) #[deb822(field = "Types", deserialize_with = deserialize_types, serialize_with = serialize_types)] pub types: HashSet, // consider alternative, closed set /// The address of the repository #[deb822(field = "URIs", deserialize_with = deserialize_uris, serialize_with = serialize_uris)] pub uris: Vec, // according to Debian that's URI, but this type is more advanced than URI from `http` crate /// The distribution name as codename or suite type (like `stable` or `testing`) #[deb822(field = "Suites", deserialize_with = deserialize_string_chain, serialize_with = serialize_string_chain)] pub suites: Vec, /// (Optional) Section of the repository, usually `main`, `contrib` or `non-free` /// return `None` if repository is Flat Repository Format (https://wiki.debian.org/DebianRepository/Format#Flat_Repository_Format) #[deb822(field = "Components", deserialize_with = deserialize_string_chain, serialize_with = serialize_string_chain)] pub components: Option>, /// (Optional) Architectures binaries from this repository run on #[deb822(field = "Architectures", deserialize_with = deserialize_string_chain, serialize_with = serialize_string_chain)] pub architectures: Vec, /// (Optional) Translations support to download #[deb822(field = "Languages", deserialize_with = deserialize_string_chain, serialize_with = serialize_string_chain)] pub languages: Option>, // TODO: Option is redundant to empty vectors /// (Optional) Download targets to acquire from this source #[deb822(field = "Targets", deserialize_with = deserialize_string_chain, serialize_with = serialize_string_chain)] pub targets: Option>, /// (Optional) Controls if APT should try PDiffs instead of downloading indexes entirely; if not set defaults to configuration option `Acquire::PDiffs` #[deb822(field = "PDiffs", deserialize_with = deserialize_yesno)] pub pdiffs: Option, /// (Optional) Controls if APT should try to acquire indexes via a URI constructed from a hashsum of the expected file #[deb822(field = "By-Hash")] pub by_hash: Option, /// (Optional) If yes circumvents parts of `apt-secure`, don't thread lightly #[deb822(field = "Allow-Insecure")] pub allow_insecure: Option, // TODO: redundant option, not present = default no /// (Optional) If yes circumvents parts of `apt-secure`, don't thread lightly #[deb822(field = "Allow-Weak")] pub allow_weak: Option, // TODO: redundant option, not present = default no /// (Optional) If yes circumvents parts of `apt-secure`, don't thread lightly #[deb822(field = "Allow-Downgrade-To-Insecure")] pub allow_downgrade_to_insecure: Option, // TODO: redundant option, not present = default no /// (Optional) If set forces whether APT considers source as rusted or no (default not present is a third state) #[deb822(field = "Trusted")] pub trusted: Option, /// (Optional) Contains either absolute path to GPG keyring or embedded GPG public key block, if not set APT uses all trusted keys; /// I can't find example of using with fingerprints #[deb822(field = "Signed-By")] pub signature: Option, /// (Optional) Field ignored by APT but used by RepoLib to identify repositories, Ubuntu sources contain them #[deb822(field = "X-Repolib-Name")] pub x_repolib_name: Option, // this supports RepoLib still used by PopOS, even if removed from Debian/Ubuntu /// (Optional) Field not present in the man page, but used in APT unit tests, potentially to hold the repository description #[deb822(field = "Description")] pub description: Option, // options: HashMap // My original parser kept remaining optional fields in the hash map, is this right approach? } impl Repository { /// Returns slice of strings containing suites for which this repository provides pub fn suites(&self) -> &[String] { self.suites.as_slice() } /// Returns the repository types (deb/deb-src) pub fn types(&self) -> &HashSet { &self.types } /// Returns the repository URIs pub fn uris(&self) -> &[Url] { &self.uris } /// Returns the repository components pub fn components(&self) -> Option<&[String]> { self.components.as_deref() } /// Returns the repository architectures pub fn architectures(&self) -> &[String] { &self.architectures } /// Generate legacy .list format lines for this repository pub fn to_legacy_format(&self) -> String { let mut lines = Vec::new(); // Generate deb and/or deb-src lines for repo_type in &self.types { let type_str = match repo_type { RepositoryType::Binary => "deb", RepositoryType::Source => "deb-src", }; for uri in &self.uris { for suite in &self.suites { let mut line = format!("{} {} {}", type_str, uri, suite); // Add components if let Some(components) = &self.components { for component in components { line.push(' '); line.push_str(component); } } lines.push(line); } } } lines.join("\n") + "\n" } /// Parse a legacy apt sources.list line (e.g., "deb http://example.com/debian stable main") pub fn parse_legacy_line(line: &str) -> Result { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 4 { return Err("Invalid repository line format".to_string()); } let repo_type = match parts[0] { "deb" => RepositoryType::Binary, "deb-src" => RepositoryType::Source, _ => return Err("Line must start with 'deb' or 'deb-src'".to_string()), }; let uri = Url::parse(parts[1]).map_err(|_| "Invalid URI".to_string())?; let suite = parts[2].to_string(); let components: Vec = parts[3..].iter().map(|s| s.to_string()).collect(); Ok(Repository { enabled: Some(true), types: HashSet::from([repo_type]), uris: vec![uri], suites: vec![suite], components: Some(components), architectures: vec![], signature: None, ..Default::default() }) } } /// Container for multiple `Repository` specifications as single `.sources` file may contain as per specification #[derive(Debug)] pub struct Repositories(Vec); impl Default for Repositories { /// Creates a default instance by loading repositories from /etc/apt/ /// /// Errors are logged as warnings (if the `tracing` feature is enabled) but /// don't prevent loading valid repositories (like APT does) fn default() -> Self { let (repos, errors) = Self::load_from_directory(std::path::Path::new("/etc/apt")); // Log any errors encountered, but continue (like APT) #[cfg(feature = "tracing")] for error in errors { tracing::warn!("Failed to load APT source: {}", error); } // Without tracing feature, errors are silently ignored #[cfg(not(feature = "tracing"))] let _ = errors; repos } } impl Repositories { /// Creates empty container of repositories pub fn empty() -> Self { Repositories(Vec::new()) } /// Creates repositories from container consisting `Repository` instances pub fn new(container: Container) -> Self where Container: Into>, { Repositories(container.into()) } /// Parse traditional sources.list format (one-line format) /// Each non-empty, non-comment line is parsed as a separate repository pub fn parse_legacy_format(content: &str) -> Result { let mut repositories = Vec::new(); for line in content.lines() { let trimmed = line.trim(); // Skip empty lines and comments if trimmed.is_empty() || trimmed.starts_with('#') { continue; } // Parse the line as a repository let repo = Repository::parse_legacy_line(trimmed)?; repositories.push(repo); } Ok(Repositories(repositories)) } /// Load repositories from a directory (e.g., /etc/apt/) /// /// This will load: /// - sources.list file from the directory /// - All *.list files from sources.list.d/ subdirectory (in lexicographical order) /// - All *.sources files from sources.list.d/ subdirectory (in lexicographical order) /// /// Returns a tuple of (successfully loaded repositories, errors encountered). /// This method is resilient like APT - errors in individual files don't prevent /// loading other valid repositories. pub fn load_from_directory(path: &Path) -> (Self, Vec) { use std::fs; let mut all_repositories = Repositories::empty(); let mut errors = Vec::new(); // Process main sources.list file if it exists let main_sources = path.join("sources.list"); if main_sources.exists() { match fs::read_to_string(&main_sources) { Ok(content) => match Self::parse_legacy_format(&content) { Ok(repos) => all_repositories.extend(repos.0), Err(e) => errors.push(LoadError::Parse { path: main_sources, error: e, }), }, Err(e) => errors.push(LoadError::Io { path: main_sources, error: e, }), } } // Process files from sources.list.d/ directory let sources_d = path.join("sources.list.d"); if !sources_d.is_dir() { return (all_repositories, errors); } let entries = match fs::read_dir(&sources_d) { Ok(entries) => entries, Err(e) => { errors.push(LoadError::DirectoryRead { path: sources_d, error: e, }); return (all_repositories, errors); } }; // Collect and sort entries lexicographically like APT does let mut entry_paths: Vec<_> = entries .filter_map(|entry| entry.ok()) .map(|entry| entry.path()) .filter(|p| p.is_file()) .filter(|p| { p.file_name() .and_then(|n| n.to_str()) .map(|n| n.ends_with(".list") || n.ends_with(".sources")) .unwrap_or(false) }) .collect(); entry_paths.sort(); for file_path in entry_paths { let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or(""); let content = match fs::read_to_string(&file_path) { Ok(content) => content, Err(e) => { errors.push(LoadError::Io { path: file_path, error: e, }); continue; } }; let parse_result = if file_name.ends_with(".list") { Self::parse_legacy_format(&content) .map(|repos| repos.0) .map_err(|e| LoadError::Parse { path: file_path, error: e, }) } else if file_name.ends_with(".sources") { content .parse::() .map(|repos| repos.0) .map_err(|e| LoadError::Parse { path: file_path, error: e, }) } else { continue; }; match parse_result { Ok(repos) => all_repositories.extend(repos), Err(e) => errors.push(e), } } (all_repositories, errors) } /// Load repositories from a directory, failing on first error /// /// Use this when you want strict error handling and need the loading /// to stop at the first problem encountered. pub fn load_from_directory_strict(path: &Path) -> Result { let (repos, errors) = Self::load_from_directory(path); if let Some(error) = errors.into_iter().next() { Err(error) } else { Ok(repos) } } /// Push a new repository pub fn push(&mut self, repo: Repository) { self.0.push(repo); } /// Retain repositories matching a predicate pub fn retain(&mut self, f: F) where F: FnMut(&Repository) -> bool, { self.0.retain(f); } /// Get iterator over repositories pub fn iter(&self) -> std::slice::Iter { self.0.iter() } /// Get mutable iterator over repositories pub fn iter_mut(&mut self) -> std::slice::IterMut { self.0.iter_mut() } /// Extend with an iterator of repositories pub fn extend(&mut self, iter: I) where I: IntoIterator, { self.0.extend(iter); } /// Check if empty pub fn is_empty(&self) -> bool { self.0.is_empty() } } impl std::str::FromStr for Repositories { type Err = String; fn from_str(s: &str) -> Result { let deb822: deb822_fast::Deb822 = s.parse().map_err(|e: deb822_fast::Error| e.to_string())?; let repos = deb822 .iter() .map(Repository::from_paragraph) .collect::, Self::Err>>()?; Ok(Repositories(repos)) } } impl std::fmt::Display for Repositories { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let result = self .0 .iter() .map(|r| { let p: deb822_fast::Paragraph = r.to_paragraph(); p.to_string() }) .collect::>() .join("\n"); write!(f, "{}", result) } } impl Deref for Repositories { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(test)] mod tests { use std::{collections::HashSet, str::FromStr}; use indoc::indoc; use url::Url; use crate::{signature::Signature, Repositories, Repository, RepositoryType}; #[test] fn test_not_machine_readable() { let s = indoc!( r#" deb [arch=arm64 signed-by=/usr/share/keyrings/docker.gpg] http://ports.ubuntu.com/ noble stable "# ); let ret = s.parse::(); assert!(ret.is_err()); //assert_eq!(ret.unwrap_err(), "Not machine readable".to_string()); assert_eq!(ret.unwrap_err(), "Unexpected token: ".to_string()); } #[test] fn test_parse_flat_repo() { let s = indoc! {r#" Types: deb URIs: http://ports.ubuntu.com/ Suites: ./ Architectures: arm64 "#}; let repos = s .parse::() .expect("Shall be parsed flawlessly"); assert!(repos[0].types.contains(&super::RepositoryType::Binary)); } #[test] fn test_parse_w_keyblock() { let s = indoc!( r#" Types: deb URIs: http://ports.ubuntu.com/ Suites: noble Components: stable Architectures: arm64 Signed-By: -----BEGIN PGP PUBLIC KEY BLOCK----- . mDMEY865UxYJKwYBBAHaRw8BAQdAd7Z0srwuhlB6JKFkcf4HU4SSS/xcRfwEQWzr crf6AEq0SURlYmlhbiBTdGFibGUgUmVsZWFzZSBLZXkgKDEyL2Jvb2t3b3JtKSA8 ZGViaWFuLXJlbGVhc2VAbGlzdHMuZGViaWFuLm9yZz6IlgQTFggAPhYhBE1k/sEZ wgKQZ9bnkfjSWFuHg9SBBQJjzrlTAhsDBQkPCZwABQsJCAcCBhUKCQgLAgQWAgMB Ah4BAheAAAoJEPjSWFuHg9SBSgwBAP9qpeO5z1s5m4D4z3TcqDo1wez6DNya27QW WoG/4oBsAQCEN8Z00DXagPHbwrvsY2t9BCsT+PgnSn9biobwX7bDDg== =5NZE -----END PGP PUBLIC KEY BLOCK----- "# ); let repos = s .parse::() .expect("Shall be parsed flawlessly"); assert!(repos[0].types.contains(&super::RepositoryType::Binary)); assert!(matches!(repos[0].signature, Some(Signature::KeyBlock(_)))); } #[test] fn test_parse_w_keypath() { let s = indoc!( r#" Types: deb URIs: http://ports.ubuntu.com/ Suites: noble Components: stable Architectures: arm64 Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg "# ); let reps = s .parse::() .expect("Shall be parsed flawlessly"); assert!(reps[0].types.contains(&super::RepositoryType::Binary)); assert!(matches!(reps[0].signature, Some(Signature::KeyPath(_)))); } #[test] fn test_serialize() { //let repos = Repositories::empty(); let repos = Repositories::new([Repository { enabled: Some(true), // TODO: looks odd, as only `Enabled: no` in meaningful types: HashSet::from([RepositoryType::Binary]), architectures: vec!["arm64".to_owned()], uris: vec![Url::from_str("https://deb.debian.org/debian").unwrap()], suites: vec!["jammy".to_owned()], components: Some(vec!["main".to_owned()]), signature: None, x_repolib_name: None, languages: None, targets: None, pdiffs: None, ..Default::default() }]); let text = repos.to_string(); assert_eq!( text, indoc! {r#" Enabled: yes Types: deb URIs: https://deb.debian.org/debian Suites: jammy Components: main Architectures: arm64 "#} ); } #[test] fn test_yesnoforce_to_string() { let yes = crate::YesNoForce::Yes; assert_eq!(yes.to_string(), "yes"); let no = crate::YesNoForce::No; assert_eq!(no.to_string(), "no"); let force = crate::YesNoForce::Force; assert_eq!(force.to_string(), "force"); } #[test] fn test_parse_legacy_line() { let line = "deb http://archive.ubuntu.com/ubuntu jammy main restricted"; let repo = Repository::parse_legacy_line(line).unwrap(); assert_eq!(repo.types.len(), 1); assert!(repo.types.contains(&RepositoryType::Binary)); assert_eq!(repo.uris.len(), 1); assert_eq!(repo.uris[0].to_string(), "http://archive.ubuntu.com/ubuntu"); assert_eq!(repo.suites, vec!["jammy".to_string()]); assert_eq!( repo.components, Some(vec!["main".to_string(), "restricted".to_string()]) ); } #[test] fn test_parse_legacy_line_deb_src() { let line = "deb-src http://archive.ubuntu.com/ubuntu jammy main"; let repo = Repository::parse_legacy_line(line).unwrap(); assert!(repo.types.contains(&RepositoryType::Source)); assert!(!repo.types.contains(&RepositoryType::Binary)); } #[test] fn test_parse_legacy_line_minimum_components() { // Test with exactly 4 parts (minimum required) let line = "deb http://example.com/debian stable main"; let repo = Repository::parse_legacy_line(line).unwrap(); assert_eq!(repo.components, Some(vec!["main".to_string()])); } #[test] fn test_parse_legacy_line_invalid() { let line = "invalid line"; let result = Repository::parse_legacy_line(line); assert!(result.is_err()); } #[test] fn test_parse_legacy_line_too_few_parts() { // Test with < 4 parts let line = "deb http://example.com/debian"; let result = Repository::parse_legacy_line(line); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Invalid repository line format"); } #[test] fn test_parse_legacy_line_invalid_type() { let line = "invalid-type http://example.com/debian stable main"; let result = Repository::parse_legacy_line(line); assert!(result.is_err()); assert_eq!( result.unwrap_err(), "Line must start with 'deb' or 'deb-src'" ); } #[test] fn test_parse_legacy_line_invalid_uri() { let line = "deb not-a-valid-uri stable main"; let result = Repository::parse_legacy_line(line); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Invalid URI"); } #[test] fn test_to_legacy_format_single_type() { let repo = Repository { types: HashSet::from([RepositoryType::Binary]), uris: vec![Url::parse("http://example.com/debian").unwrap()], suites: vec!["stable".to_string()], components: Some(vec!["main".to_string()]), ..Default::default() }; let legacy = repo.to_legacy_format(); assert_eq!(legacy, "deb http://example.com/debian stable main\n"); } #[test] fn test_to_legacy_format_both_types() { let repo = Repository { types: HashSet::from([RepositoryType::Binary, RepositoryType::Source]), uris: vec![Url::parse("http://example.com/debian").unwrap()], suites: vec!["stable".to_string()], components: Some(vec!["main".to_string(), "contrib".to_string()]), ..Default::default() }; let legacy = repo.to_legacy_format(); // Should contain both deb and deb-src lines assert!(legacy.contains("deb http://example.com/debian stable main contrib")); assert!(legacy.contains("deb-src http://example.com/debian stable main contrib")); } #[test] fn test_to_legacy_format_multiple_uris_and_suites() { let repo = Repository { types: HashSet::from([RepositoryType::Binary]), uris: vec![ Url::parse("http://example1.com/debian").unwrap(), Url::parse("http://example2.com/debian").unwrap(), ], suites: vec!["stable".to_string(), "testing".to_string()], components: Some(vec!["main".to_string()]), ..Default::default() }; let legacy = repo.to_legacy_format(); // Should generate a line for each URI/suite combination assert!(legacy.contains("deb http://example1.com/debian stable main")); assert!(legacy.contains("deb http://example1.com/debian testing main")); assert!(legacy.contains("deb http://example2.com/debian stable main")); assert!(legacy.contains("deb http://example2.com/debian testing main")); } #[test] fn test_to_legacy_format_no_components() { let repo = Repository { types: HashSet::from([RepositoryType::Binary]), uris: vec![Url::parse("http://example.com/debian").unwrap()], suites: vec!["stable".to_string()], components: None, ..Default::default() }; let legacy = repo.to_legacy_format(); assert_eq!(legacy, "deb http://example.com/debian stable\n"); } #[test] fn test_repository_type_display() { assert_eq!(RepositoryType::Binary.to_string(), "deb"); assert_eq!(RepositoryType::Source.to_string(), "deb-src"); } #[test] fn test_yesnoforce_display() { assert_eq!(crate::YesNoForce::Yes.to_string(), "yes"); assert_eq!(crate::YesNoForce::No.to_string(), "no"); assert_eq!(crate::YesNoForce::Force.to_string(), "force"); } #[test] fn test_repositories_is_empty() { let empty_repos = Repositories::empty(); assert!(empty_repos.is_empty()); let mut repos = Repositories::empty(); repos.push(Repository::default()); assert!(!repos.is_empty()); } #[test] fn test_repository_getters() { let repo = Repository { types: HashSet::from([RepositoryType::Binary, RepositoryType::Source]), uris: vec![Url::parse("http://example.com/debian").unwrap()], suites: vec!["stable".to_string()], components: Some(vec!["main".to_string(), "contrib".to_string()]), architectures: vec!["amd64".to_string(), "arm64".to_string()], ..Default::default() }; // Test types getter assert_eq!( repo.types(), &HashSet::from([RepositoryType::Binary, RepositoryType::Source]) ); // Test uris getter assert_eq!(repo.uris().len(), 1); assert_eq!(repo.uris()[0].to_string(), "http://example.com/debian"); // Test suites getter (existing) assert_eq!(repo.suites(), vec!["stable"]); // Test components getter assert_eq!( repo.components(), Some(vec!["main".to_string(), "contrib".to_string()].as_slice()) ); // Test architectures getter assert_eq!(repo.architectures(), vec!["amd64", "arm64"]); } #[test] fn test_repositories_iter() { let mut repos = Repositories::empty(); repos.push(Repository { suites: vec!["stable".to_string()], ..Default::default() }); repos.push(Repository { suites: vec!["testing".to_string()], ..Default::default() }); // Test iter() let suites: Vec<_> = repos.iter().map(|r| r.suites()).collect(); assert_eq!(suites.len(), 2); assert_eq!(suites[0], vec!["stable"]); assert_eq!(suites[1], vec!["testing"]); // Test iter_mut() - modifying through mutable iterator for repo in repos.iter_mut() { repo.enabled = Some(false); } for repo in repos.iter() { assert_eq!(repo.enabled, Some(false)); } } #[test] fn test_parse_legacy_format() { let content = indoc! {r#" # This is a comment deb http://archive.ubuntu.com/ubuntu jammy main restricted deb-src http://archive.ubuntu.com/ubuntu jammy main restricted # Another comment deb http://security.ubuntu.com/ubuntu jammy-security main "#}; let repos = Repositories::parse_legacy_format(content).unwrap(); assert_eq!(repos.len(), 3); // First repository assert!(repos[0].types().contains(&RepositoryType::Binary)); assert_eq!( repos[0].uris()[0].to_string(), "http://archive.ubuntu.com/ubuntu" ); assert_eq!(repos[0].suites(), vec!["jammy"]); assert_eq!( repos[0].components(), Some(vec!["main".to_string(), "restricted".to_string()].as_slice()) ); // Second repository assert!(repos[1].types().contains(&RepositoryType::Source)); // Third repository assert_eq!(repos[2].suites(), vec!["jammy-security"]); assert_eq!( repos[2].components(), Some(vec!["main".to_string()].as_slice()) ); } } apt-sources-0.1.1/src/ppa.rs000064400000000000000000000072171046102023000140140ustar 00000000000000//! This module provides functionality for handling Personal Package Archives (PPAs) in a //! Debian/Ubuntu context. use url::Url; /// Default URL for Launchpad PPAs pub const LAUNCHPAD_PPA_URL: &str = "https://ppa.launchpadcontent.net"; /// Valid components for PPAs pub const VALID_PPA_COMPONENTS: &[&str] = &["main", "main/debug"]; /// Information about a PPA (Personal Package Archive) #[derive(Debug, Clone)] pub struct PpaInfo { /// The PPA owner's username pub user: String, /// The PPA name pub name: String, } impl PpaInfo { /// Parse a PPA specification string (e.g., "ppa:user/ppa-name") pub fn parse(ppa_spec: &str) -> Result { if !ppa_spec.starts_with("ppa:") { return Err("Not a PPA format".to_string()); } let ppa_part = &ppa_spec[4..]; let parts: Vec<&str> = ppa_part.split('/').collect(); if parts.len() != 2 { return Err("Invalid PPA format. Expected ppa:user/ppa-name".to_string()); } Ok(PpaInfo { user: parts[0].to_string(), name: parts[1].to_string(), }) } /// Generate the repository URL for this PPA pub fn repository_url(&self, _codename: &str) -> Result { Url::parse(&format!( "{}/{}/{}/ubuntu", LAUNCHPAD_PPA_URL, self.user, self.name )) .map_err(|e| format!("Failed to construct PPA URL: {}", e)) } /// Generate a filename for this PPA pub fn filename(&self, extension: &str) -> String { format!("{}-ubuntu-{}.{}", self.user, self.name, extension) } } /// Validate PPA components pub fn validate_ppa_components(components: &[String]) -> Result<(), String> { for component in components { if !VALID_PPA_COMPONENTS.contains(&component.as_str()) { return Err(format!( "Invalid component '{}' for PPA.\n\ Valid components are: {}\n\ Suggestion: Use 'main' for regular packages or 'main/debug' for debug symbols.", component, VALID_PPA_COMPONENTS.join(", ") )); } } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_ppa_format() { // Valid PPA let ppa = PpaInfo::parse("ppa:user/repo").unwrap(); assert_eq!(ppa.user, "user"); assert_eq!(ppa.name, "repo"); // Invalid formats assert!(PpaInfo::parse("not-a-ppa").is_err()); assert!(PpaInfo::parse("ppa:invalid").is_err()); assert!(PpaInfo::parse("ppa:too/many/parts").is_err()); } #[test] fn test_validate_ppa_components() { assert!(validate_ppa_components(&["main".to_string()]).is_ok()); assert!(validate_ppa_components(&["main/debug".to_string()]).is_ok()); assert!(validate_ppa_components(&["invalid".to_string()]).is_err()); } #[test] fn test_ppa_filename() { let ppa = PpaInfo { user: "test-user".to_string(), name: "test-repo".to_string(), }; assert_eq!(ppa.filename("list"), "test-user-ubuntu-test-repo.list"); assert_eq!( ppa.filename("sources"), "test-user-ubuntu-test-repo.sources" ); // Test with empty extension assert_eq!(ppa.filename(""), "test-user-ubuntu-test-repo."); // Test with special characters (they remain as-is) let ppa_special = PpaInfo { user: "user_123".to_string(), name: "repo-name".to_string(), }; assert_eq!( ppa_special.filename("list"), "user_123-ubuntu-repo-name.list" ); } } apt-sources-0.1.1/src/signature.rs000064400000000000000000000050331046102023000152270ustar 00000000000000//! A module implementing `Signature` type that holds info about variants of the signature key used by the repository use std::path::PathBuf; use crate::error::RepositoryError; /// A type to store #[derive(Debug, PartialEq, Clone)] pub enum Signature { /// The PGP key is stored inside the `.sources` files KeyBlock(String), // TODO: shall we validate PGP Public Key? /// The public key is store in a file of the given path KeyPath(PathBuf), // TODO: man page specifies fingerprints, but there's no example } impl std::str::FromStr for Signature { type Err = RepositoryError; fn from_str(text: &str) -> Result { // Normal examples say PGP line shall start next line after `Signed-By` field // but all my files have it starting after a space in the same line and that works. // It's quite confusing, but let it be... we have to deal with reality. if text.contains("\n") { // If text is multiline, we assume PGP Public Key block Ok(Signature::KeyBlock(text.to_string())) } else { // otherwise one-liner is a path Ok(Signature::KeyPath(text.into())) } // if let Some((name, rest)) = text.split_once('\n') { // if name.is_empty() { // println!("& Name = {}", name); // Ok(Signature::KeyBlock(rest.to_string())) // } else { // println!("& Name = {}", name); // Err(RepositoryError::InvalidSignature) // } // } else { // println!("& No name"); // Ok(Signature::KeyPath(text.into())) // } } } impl std::fmt::Display for Signature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Signature::KeyBlock(text) => write!(f, "\n{}", text), Signature::KeyPath(path) => f.write_str(path.to_string_lossy().as_ref()), } } } #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; #[test] fn test_signature_display() { // Test KeyPath display let path_sig = Signature::KeyPath(PathBuf::from("/etc/apt/trusted.gpg")); assert_eq!(path_sig.to_string(), "/etc/apt/trusted.gpg"); // Test KeyBlock display let key_block = "-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest key\n-----END PGP PUBLIC KEY BLOCK-----"; let block_sig = Signature::KeyBlock(key_block.to_string()); assert_eq!(block_sig.to_string(), format!("\n{}", key_block)); } } apt-sources-0.1.1/src/sources_manager.rs000064400000000000000000000670361046102023000164160ustar 00000000000000use crate::{Repositories, Repository, RepositoryType}; use std::collections::HashSet; use std::fs; use std::io; use std::path::{Path, PathBuf}; /// Default path for APT sources files pub const DEFAULT_SOURCES_PATH: &str = "/etc/apt/sources.list.d"; /// Default path for APT keyring files pub const DEFAULT_KEYRING_PATH: &str = "/etc/apt/trusted.gpg.d"; /// Manager for APT sources and keyrings #[derive(Debug, Clone)] pub struct SourcesManager { sources_dir: PathBuf, keyring_dir: PathBuf, } impl Default for SourcesManager { fn default() -> Self { Self { sources_dir: PathBuf::from(DEFAULT_SOURCES_PATH), keyring_dir: PathBuf::from(DEFAULT_KEYRING_PATH), } } } impl SourcesManager { /// Create a new SourcesManager with custom directories pub fn new(sources_dir: impl Into, keyring_dir: impl Into) -> Self { Self { sources_dir: sources_dir.into(), keyring_dir: keyring_dir.into(), } } /// Get the path to the sources directory pub fn sources_dir(&self) -> &Path { &self.sources_dir } /// Get the path to the keyring directory pub fn keyring_dir(&self) -> &Path { &self.keyring_dir } /// Generate a filename for a repository pub fn generate_filename(&self, name: &str, format: FileFormat) -> String { let sanitized = name.replace(['/', ':', ' '], "-").to_lowercase(); match format { FileFormat::Deb822 => format!("{}.sources", sanitized), FileFormat::Legacy => format!("{}.list", sanitized), } } /// Get the full path for a repository file pub fn get_repository_path(&self, filename: &str) -> PathBuf { self.sources_dir.join(filename) } /// Get the full path for a keyring file pub fn get_keyring_path(&self, filename: &str) -> PathBuf { self.keyring_dir.join(filename) } /// Write repositories to a file pub fn write_repositories(&self, path: &Path, repositories: &Repositories) -> io::Result<()> { let content = repositories.to_string(); fs::write(path, content) } /// Read repositories from a file pub fn read_repositories(&self, path: &Path) -> Result { let content = fs::read_to_string(path) .map_err(|e| format!("Failed to read file {}: {}", path.display(), e))?; content .parse::() .map_err(|e| format!("Failed to parse repositories: {}", e)) } /// List all repository files in the sources directory pub fn list_repository_files(&self) -> io::Result> { let mut files = Vec::new(); if self.sources_dir.exists() { for entry in fs::read_dir(&self.sources_dir)? { let entry = entry?; let path = entry.path(); if path.is_file() { if let Some(ext) = path.extension() { if ext == "sources" || ext == "list" { files.push(path); } } } } } Ok(files) } /// Scan all repository files and return their contents pub fn scan_all_repositories(&self) -> Result, String> { let mut results = Vec::new(); let files = self .list_repository_files() .map_err(|e| format!("Failed to list repository files: {}", e))?; for file in files { match self.read_repositories(&file) { Ok(repos) => results.push((file, repos)), Err(e) => { // Log error but continue scanning eprintln!("Warning: Failed to read {}: {}", file.display(), e); } } } Ok(results) } /// Check if a repository already exists in any file pub fn repository_exists(&self, repository: &Repository) -> Result, String> { let all_repos = self.scan_all_repositories()?; for (path, repos) in all_repos { for repo in repos.iter() { if repos_match(repo, repository) { return Ok(Some(path)); } } } Ok(None) } /// Ensure the sources and keyring directories exist pub fn ensure_directories(&self) -> io::Result<()> { fs::create_dir_all(&self.sources_dir)?; fs::create_dir_all(&self.keyring_dir)?; Ok(()) } /// Add a repository to a file, creating the file if it doesn't exist pub fn add_repository(&self, repository: &Repository, filename: &str) -> Result<(), String> { let path = self.get_repository_path(filename); // Check if repository already exists if let Some(existing_path) = self.repository_exists(repository)? { return Err(format!( "Repository already exists in {}", existing_path.display() )); } // Read existing repositories if file exists let mut repositories = if path.exists() { self.read_repositories(&path)? } else { Repositories::empty() }; // Add the new repository repositories.push(repository.clone()); // Write back to file self.write_repositories(&path, &repositories) .map_err(|e| format!("Failed to write repository: {}", e)) } /// Remove a repository from all files pub fn remove_repository(&self, repository: &Repository) -> Result { let mut removed = false; let all_files = self.scan_all_repositories()?; for (path, mut repos) in all_files { let initial_count = repos.len(); repos.retain(|r| !repos_match(r, repository)); if repos.len() < initial_count { removed = true; if repos.is_empty() { // Remove empty file fs::remove_file(&path) .map_err(|e| format!("Failed to remove {}: {}", path.display(), e))?; } else { // Write updated repositories self.write_repositories(&path, &repos) .map_err(|e| format!("Failed to update {}: {}", path.display(), e))?; } } } Ok(removed) } /// Enable or disable a repository pub fn set_repository_enabled( &self, repository: &Repository, enabled: bool, ) -> Result { let mut modified = false; let all_files = self.scan_all_repositories()?; for (path, mut repos) in all_files { let mut changed = false; for repo in repos.iter_mut() { if repos_match(repo, repository) && repo.enabled != Some(enabled) { repo.enabled = Some(enabled); changed = true; modified = true; } } if changed { self.write_repositories(&path, &repos) .map_err(|e| format!("Failed to update {}: {}", path.display(), e))?; } } Ok(modified) } /// Add a component to all matching repositories pub fn add_component_to_repositories( &self, component: &str, filter: impl Fn(&Repository) -> bool, ) -> Result { let mut modified_count = 0; let all_files = self.scan_all_repositories()?; for (path, mut repos) in all_files { let mut changed = false; for repo in repos.iter_mut() { if filter(repo) { if let Some(components) = &mut repo.components { if !components.contains(&component.to_string()) { components.push(component.to_string()); changed = true; modified_count += 1; } } else { repo.components = Some(vec![component.to_string()]); changed = true; modified_count += 1; } } } if changed { self.write_repositories(&path, &repos) .map_err(|e| format!("Failed to update {}: {}", path.display(), e))?; } } Ok(modified_count) } /// Enable source repositories pub fn enable_source_repositories( &self, create_if_missing: bool, ) -> Result<(u32, u32), String> { let mut enabled_count = 0; let mut created_count = 0; let all_files = self.scan_all_repositories()?; for (path, mut repos) in all_files { let mut changed = false; let mut new_repos = Vec::new(); for repo in repos.iter_mut() { // Check if this repo has binary type but not source if repo.types.contains(&RepositoryType::Binary) && !repo.types.contains(&RepositoryType::Source) { if repo.enabled == Some(false) { // Just enable existing disabled source repo repo.types.insert(RepositoryType::Source); repo.enabled = Some(true); enabled_count += 1; changed = true; } else if create_if_missing { // Create a new source repository entry let mut source_repo = repo.clone(); source_repo.types = HashSet::from([RepositoryType::Source]); new_repos.push(source_repo); created_count += 1; changed = true; } } } // Add any new repositories repos.extend(new_repos); if changed { self.write_repositories(&path, &repos) .map_err(|e| format!("Failed to update {}: {}", path.display(), e))?; } } Ok((enabled_count, created_count)) } /// List all repositories with their file paths pub fn list_all_repositories(&self) -> Result, String> { let files = self.scan_all_repositories()?; // Pre-calculate capacity to avoid reallocations let total_repos: usize = files.iter().map(|(_, repos)| repos.len()).sum(); let mut all_repos = Vec::with_capacity(total_repos); for (path, repos) in files { for repo in repos.iter() { all_repos.push((path.clone(), repo.clone())); } } Ok(all_repos) } /// Generate a keyring filename for a repository pub fn generate_keyring_filename(&self, repository_name: &str) -> String { let sanitized = repository_name.replace(['/', ':', ' '], "-").to_lowercase(); format!("{}.gpg", sanitized) } /// Save a GPG key to the keyring directory pub fn save_key(&self, key_data: &[u8], filename: &str) -> io::Result { let key_path = self.get_keyring_path(filename); fs::write(&key_path, key_data)?; Ok(key_path) } } /// File format for APT source list files #[derive(Debug, Clone, Copy, PartialEq)] pub enum FileFormat { /// Deb822 format (new style) Deb822, /// Legacy format (one-line style) Legacy, } /// Check if two repositories match (have the same URIs, suites, and components) fn repos_match(repo1: &Repository, repo2: &Repository) -> bool { // Compare types if repo1.types != repo2.types { return false; } // Compare URIs let uris1: HashSet<_> = repo1.uris.iter().collect(); let uris2: HashSet<_> = repo2.uris.iter().collect(); if uris1 != uris2 { return false; } // Compare suites let suites1: HashSet<_> = repo1.suites.iter().collect(); let suites2: HashSet<_> = repo2.suites.iter().collect(); if suites1 != suites2 { return false; } // Compare components let components1: HashSet<_> = repo1.components.iter().collect(); let components2: HashSet<_> = repo2.components.iter().collect(); if components1 != components2 { return false; } true } #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; use url::Url; fn create_test_manager() -> (SourcesManager, TempDir) { let temp_dir = TempDir::new().unwrap(); let sources_dir = temp_dir.path().join("sources.list.d"); let keyring_dir = temp_dir.path().join("trusted.gpg.d"); let manager = SourcesManager::new(&sources_dir, &keyring_dir); (manager, temp_dir) } fn create_test_repository() -> Repository { Repository { enabled: Some(true), types: HashSet::from([RepositoryType::Binary]), uris: vec![Url::parse("http://example.com/ubuntu").unwrap()], suites: vec!["focal".to_string()], components: Some(vec!["main".to_string()]), architectures: vec!["amd64".to_string()], ..Default::default() } } #[test] fn test_ensure_directories() { let (manager, _temp_dir) = create_test_manager(); assert!(!manager.sources_dir().exists()); assert!(!manager.keyring_dir().exists()); manager.ensure_directories().unwrap(); assert!(manager.sources_dir().exists()); assert!(manager.keyring_dir().exists()); } #[test] fn test_generate_filename() { let (manager, _) = create_test_manager(); assert_eq!( manager.generate_filename("test-repo", FileFormat::Deb822), "test-repo.sources" ); assert_eq!( manager.generate_filename("Test/Repo:Name", FileFormat::Legacy), "test-repo-name.list" ); } #[test] fn test_add_repository() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo = create_test_repository(); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Verify it was added let path = manager.get_repository_path("test.sources"); assert!(path.exists()); let repos = manager.read_repositories(&path).unwrap(); assert_eq!(repos.len(), 1); assert_eq!(repos[0].uris[0].as_str(), "http://example.com/ubuntu"); // Try to add duplicate - should fail let result = manager.add_repository(&repo, "test2.sources"); assert!(result.is_err()); assert!(result.unwrap_err().contains("already exists")); } #[test] fn test_remove_repository() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo = create_test_repository(); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Remove it let removed = manager.remove_repository(&repo).unwrap(); assert!(removed); // Verify file was removed let path = manager.get_repository_path("test.sources"); assert!(!path.exists()); // Try to remove again - should return false let removed = manager.remove_repository(&repo).unwrap(); assert!(!removed); } #[test] fn test_set_repository_enabled() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let mut repo = create_test_repository(); repo.enabled = Some(true); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Disable it let modified = manager.set_repository_enabled(&repo, false).unwrap(); assert!(modified); // Verify it was disabled let path = manager.get_repository_path("test.sources"); let repos = manager.read_repositories(&path).unwrap(); assert_eq!(repos[0].enabled, Some(false)); // Enable it again let modified = manager.set_repository_enabled(&repo, true).unwrap(); assert!(modified); // Verify it was enabled let repos = manager.read_repositories(&path).unwrap(); assert_eq!(repos[0].enabled, Some(true)); } #[test] fn test_add_component_to_repositories() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo = create_test_repository(); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Add component to repositories from example.com let count = manager .add_component_to_repositories("universe", |r| { r.uris.iter().any(|u| u.host_str() == Some("example.com")) }) .unwrap(); assert_eq!(count, 1); // Verify component was added let path = manager.get_repository_path("test.sources"); let repos = manager.read_repositories(&path).unwrap(); assert!(repos[0] .components .as_ref() .unwrap() .contains(&"universe".to_string())); // Try to add same component again - should not modify let count = manager .add_component_to_repositories("universe", |r| { r.uris.iter().any(|u| u.host_str() == Some("example.com")) }) .unwrap(); assert_eq!(count, 0); } #[test] fn test_enable_source_repositories() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo = create_test_repository(); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Enable source repositories (create if missing) let (enabled, created) = manager.enable_source_repositories(true).unwrap(); assert_eq!(enabled, 0); assert_eq!(created, 1); // Verify source repo was created let path = manager.get_repository_path("test.sources"); let repos = manager.read_repositories(&path).unwrap(); assert_eq!(repos.len(), 2); // Find the source repo let source_repo = repos .iter() .find(|r| r.types.contains(&RepositoryType::Source)) .unwrap(); assert!(source_repo.types.contains(&RepositoryType::Source)); assert!(!source_repo.types.contains(&RepositoryType::Binary)); } #[test] fn test_list_repository_files() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); // Initially empty let files = manager.list_repository_files().unwrap(); assert_eq!(files.len(), 0); // Add some files let repo1 = create_test_repository(); let mut repo2 = create_test_repository(); repo2.suites = vec!["jammy".to_string()]; // Make it different manager.add_repository(&repo1, "test1.sources").unwrap(); manager.add_repository(&repo2, "test2.list").unwrap(); // Should find both files let files = manager.list_repository_files().unwrap(); assert_eq!(files.len(), 2); // Create a non-repository file let non_repo = manager.get_repository_path("test.txt"); fs::write(&non_repo, "not a repo").unwrap(); // Should still only find 2 repository files let files = manager.list_repository_files().unwrap(); assert_eq!(files.len(), 2); } #[test] fn test_scan_all_repositories() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo1 = create_test_repository(); let mut repo2 = create_test_repository(); repo2.uris = vec![Url::parse("http://example2.com/ubuntu").unwrap()]; // Add repositories to different files manager.add_repository(&repo1, "test1.sources").unwrap(); manager.add_repository(&repo2, "test2.sources").unwrap(); // Scan all let all_repos = manager.scan_all_repositories().unwrap(); assert_eq!(all_repos.len(), 2); // Each file should have one repository for (_, repos) in all_repos { assert_eq!(repos.len(), 1); } } #[test] fn test_repository_exists() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let repo = create_test_repository(); // Should not exist initially assert!(manager.repository_exists(&repo).unwrap().is_none()); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Should exist now let existing_path = manager.repository_exists(&repo).unwrap(); assert!(existing_path.is_some()); assert!(existing_path.unwrap().ends_with("test.sources")); } #[test] fn test_save_key() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let key_data = b"-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest key\n-----END PGP PUBLIC KEY BLOCK-----"; let key_path = manager.save_key(key_data, "test.gpg").unwrap(); assert!(key_path.exists()); assert_eq!(key_path.file_name().unwrap(), "test.gpg"); let saved_data = fs::read(&key_path).unwrap(); assert_eq!(saved_data, key_data); } #[test] fn test_repos_match() { let repo1 = create_test_repository(); let mut repo2 = repo1.clone(); // Should match identical repos assert!(repos_match(&repo1, &repo2)); // Different types repo2.types.insert(RepositoryType::Source); assert!(!repos_match(&repo1, &repo2)); repo2.types = repo1.types.clone(); // Different URIs repo2.uris.push(Url::parse("http://extra.com").unwrap()); assert!(!repos_match(&repo1, &repo2)); repo2.uris = repo1.uris.clone(); // Different suites repo2.suites.push("bionic".to_string()); assert!(!repos_match(&repo1, &repo2)); repo2.suites = repo1.suites.clone(); // Different components if let Some(ref mut components) = repo2.components { components.push("universe".to_string()); } assert!(!repos_match(&repo1, &repo2)); } #[test] fn test_generate_keyring_filename() { let (manager, _) = create_test_manager(); // Test basic filename assert_eq!( manager.generate_keyring_filename("test-repo"), "test-repo.gpg" ); // Test with special characters that should be sanitized assert_eq!( manager.generate_keyring_filename("Test/Repo:Name With Spaces"), "test-repo-name-with-spaces.gpg" ); // Test empty string assert_eq!(manager.generate_keyring_filename(""), ".gpg"); } #[test] fn test_list_all_repositories() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); // Initially empty let all_repos = manager.list_all_repositories().unwrap(); assert!(all_repos.is_empty()); // Add some repositories let repo1 = create_test_repository(); let mut repo2 = create_test_repository(); repo2.suites = vec!["jammy".to_string()]; manager.add_repository(&repo1, "test1.sources").unwrap(); manager.add_repository(&repo2, "test2.sources").unwrap(); // Should list all repositories with their paths let all_repos = manager.list_all_repositories().unwrap(); assert_eq!(all_repos.len(), 2); // Check that paths are included let paths: Vec<_> = all_repos .iter() .map(|(p, _)| p.file_name().unwrap()) .collect(); assert!(paths.contains(&std::ffi::OsStr::new("test1.sources"))); assert!(paths.contains(&std::ffi::OsStr::new("test2.sources"))); } #[test] fn test_enable_source_repositories_counter_edge_cases() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); // Add a binary repository let mut repo = create_test_repository(); repo.types = HashSet::from([RepositoryType::Binary]); manager.add_repository(&repo, "test.sources").unwrap(); // Enable source repositories with creation let (enabled, created) = manager.enable_source_repositories(true).unwrap(); assert_eq!(enabled, 0); assert_eq!(created, 1); // Check that source repo was actually created let all_repos = manager.list_all_repositories().unwrap(); let source_repos: Vec<_> = all_repos .iter() .filter(|(_, r)| { r.types.contains(&RepositoryType::Source) && !r.types.contains(&RepositoryType::Binary) }) .collect(); assert_eq!(source_repos.len(), 1); // Add a disabled binary repository let mut disabled_repo = create_test_repository(); disabled_repo.types = HashSet::from([RepositoryType::Binary]); disabled_repo.enabled = Some(false); disabled_repo.suites = vec!["jammy".to_string()]; // Make it different manager .add_repository(&disabled_repo, "test2.sources") .unwrap(); // Enable source repositories again // The first binary repo will create another source repo // The disabled binary repo will have source type added and be enabled let (enabled2, created2) = manager.enable_source_repositories(true).unwrap(); assert_eq!(enabled2, 1); // Should enable the disabled binary repo assert_eq!(created2, 1); // Will create a source repo for the new binary repo } #[test] fn test_set_repository_enabled_edge_cases() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let mut repo = create_test_repository(); repo.enabled = Some(true); // Add repository manager.add_repository(&repo, "test.sources").unwrap(); // Try to enable already enabled repo - should return false let modified = manager.set_repository_enabled(&repo, true).unwrap(); assert!(!modified); // Disable it let modified = manager.set_repository_enabled(&repo, false).unwrap(); assert!(modified); // Try to disable already disabled repo - should return false let modified = manager.set_repository_enabled(&repo, false).unwrap(); assert!(!modified); } #[test] fn test_add_component_edge_cases() { let (manager, _) = create_test_manager(); manager.ensure_directories().unwrap(); let mut repo = create_test_repository(); repo.components = Some(vec!["main".to_string()]); manager.add_repository(&repo, "test.sources").unwrap(); // Add component that already exists - should not increment counter let count = manager .add_component_to_repositories("main", |_| true) .unwrap(); assert_eq!(count, 0); // Add new component let count = manager .add_component_to_repositories("universe", |_| true) .unwrap(); assert_eq!(count, 1); // Add to repo with no components initially let mut repo2 = create_test_repository(); repo2.components = None; manager.add_repository(&repo2, "test2.sources").unwrap(); let count = manager .add_component_to_repositories("restricted", |r| r.components.is_none()) .unwrap(); assert_eq!(count, 1); } }