commit bc6eb2047bb5f15be01f0fdd513aad06f54eba64 Author: jane400 Date: Fri Aug 16 19:40:14 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bac941 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +private.rs \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..df14c63 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "languageToolLinter.languageTool.ignoredWordsInWorkspace": [ + "libpaket" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..be099df --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3752 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca2be1d5c43812bae364ee3f30b3afcb7877cf59f4aeb94c66f313a41d2fac9" + +[[package]] +name = "cairo-rs" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797fd5a634dcb0ad0d7d583df794deb0a236d88e759cd34b7da20198c6c9d145" +dependencies = [ + "bitflags 2.6.0", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +dependencies = [ + "cookie", + "idna 0.5.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[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 = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + +[[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 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[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 = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28bb53ecb56857c683c9ec859908e076dd3969c7d67598bd8b1ce095d211304a" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f6681a0c1330d1d3968bec1529f7172d62819ef0bdbb0d18022320654158b03" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7d7237c1487ed4b300aac7744efcbf1319e12d60d7afcd6f505414bd5b5dea" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67576c8ec012156d7f680e201a807b4432a77babb3157e0555e990ab6bcd878" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gio" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398e3da68749fdc32783cbf7521ec3f65c9cf946db8c7774f8460af49e52c6e2" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4feb96b31c32730ea3e1e89aecd2e4e37ecb1c473ad8f685e3430a159419f63" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.52.0", +] + +[[package]] +name = "glib" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee90a615ce05be7a32932cfb8adf2c4bbb4700e80d37713c981fb24c0c56238" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da558d8177c0c8c54368818b508a4244e1286fce2858cef4e547023f0cfa5ef" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4958c26e5a01c9af00dea669a97369eccbec29a8e6d125c24ea2d85ee7467b60" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glycin" +version = "2.0.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad0e7b971597fd206fbf0a6fb37608fd7983705ef8adfb8781fcbc155ef8f83" +dependencies = [ + "async-fs", + "async-io", + "async-lock", + "blocking", + "futures-channel", + "futures-timer", + "futures-util", + "gdk4", + "gio", + "glycin-utils", + "gufo-common", + "gufo-exif", + "lcms2", + "lcms2-sys", + "libc", + "libseccomp", + "memfd", + "memmap2", + "nix", + "static_assertions", + "thiserror", + "tracing", + "yeslogic-fontconfig-sys", + "zbus", +] + +[[package]] +name = "glycin-utils" +version = "2.0.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e5bd3f73d2df60927b5eca9cdc9aee064dc7df8720c8bfb73f815f6a17d97" +dependencies = [ + "env_logger", + "gufo-common", + "libc", + "libseccomp", + "log", + "memmap2", + "nix", + "paste", + "rmp-serde", + "serde", + "thiserror", + "zbus", +] + +[[package]] +name = "gobject-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6908864f5ffff15b56df7e90346863904f49b949337ed0456b9287af61903b8" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630e940ad5824f90221d6579043a9cd1f8bec86b4a17faaf7827d58eb16e8c1f" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8fade7b754982f47ebbed241fd2680816fdd4598321784da10b9e1168836a" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3cf2091e1af185b347b3450817d93dea6fe435df7abd4c2cd7fb5bcb4cfda8" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa69614a26d8760c186c3690f1b0fbb917572ca23ef83137445770ceddf8cde" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaffc6c743c9160514cc9b67eace364e5dc5798369fa809cdb04e035c21c5c5d" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188211f546ce5801f6d0245c37b6249143a2cb4fa040e54829ca1e76796e9f09" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1114a207af8ada02cf4658a76692f4190f06f093380d5be07e3ca8b43aa7c666" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gufo-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1af2c3c6b244761a93e30989fa0868b92e386b6eb817fc6ced405462af4a7db" +dependencies = [ + "once_cell", + "paste", + "serde", +] + +[[package]] +name = "gufo-exif" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2aff5e56872f03d477b55cccef818e6cdae482485c39c130ff9b3b31b69ea10" +dependencies = [ + "gufo-common", + "thiserror", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "javascriptcore6" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b28ed9c7c08f906b2a51bc2365eae2ba5e7db1249b89892f7ae4cbd602d1f4" +dependencies = [ + "glib", + "javascriptcore6-sys", + "libc", +] + +[[package]] +name = "javascriptcore6-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4741e2a31c2145050dd4971f8dd51e92c840d5839a7124cc68a33c7325523a12" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lcms2" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680ec3fa42c36e0af9ca02f20a3742a82229c7f1ee0e6754294de46a80be6f74" +dependencies = [ + "bytemuck", + "foreign-types 0.5.0", + "lcms2-sys", +] + +[[package]] +name = "lcms2-sys" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "593265f9a3172180024fb62580ee31348f31be924b19416da174ebb7fb623d2e" +dependencies = [ + "cc", + "dunce", + "libc", + "pkg-config", +] + +[[package]] +name = "libadwaita" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff9c222b5c783729de45185f07b2fec2d43a7f9c63961e777d3667e20443878" +dependencies = [ + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c44d8bdbad31d6639e1f20cc9c1424f1a8e02d751fc28d44659bf743fb9eca6" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libpaket" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "base64", + "ed25519-dalek", + "hmac", + "num_enum", + "rand", + "random-string", + "reqwest", + "secrecy", + "serde", + "serde_json", + "serde_newtype", + "serde_repr", + "sha2", + "thiserror", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "libseccomp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21c57fd8981a80019807b7b68118618d29a87177c63d704fc96e6ecd003ae5b3" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libseccomp-sys", + "pkg-config", +] + +[[package]] +name = "libseccomp-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7cbbd4ad467251987c6e5b47d53b11a5a05add08f2447a9e2d70aef1e0d138" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix", +] + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[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.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "object" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oo7" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" +dependencies = [ + "aes", + "async-fs", + "async-io", + "async-lock", + "async-net", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "rand", + "serde", + "sha2", + "subtle", + "zbus", + "zeroize", + "zvariant", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types 0.3.2", + "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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "paket" +version = "0.1.0" +dependencies = [ + "glycin", + "libadwaita", + "libpaket", + "oo7", + "relm4", + "relm4-components", + "relm4-macros", + "reqwest", + "tracker", + "webkit6", +] + +[[package]] +name = "pango" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54768854025df6903061d0084fd9702a253ddfd60db7d9b751d43b76689a7f0a" +dependencies = [ + "gio", + "glib", + "libc", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07cc57d10cee4ec661f718a6902cee18c2f4cfae08e87e5a390525946913390" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "random-string" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70fd13c3024ae3f17381bb5c4d409c6dc9ea6895c08fa2147aba305bea3c4af" +dependencies = [ + "fastrand 1.9.0", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "relm4" +version = "0.9.0" +dependencies = [ + "flume", + "fragile", + "futures", + "gtk4", + "libadwaita", + "once_cell", + "relm4-css", + "relm4-macros", + "tokio", + "tracing", +] + +[[package]] +name = "relm4-components" +version = "0.9.0" +dependencies = [ + "once_cell", + "relm4", + "tracker", +] + +[[package]] +name = "relm4-css" +version = "0.9.0" + +[[package]] +name = "relm4-macros" +version = "0.9.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "async-compression", + "base64", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_newtype" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a7687f62867855f32a5863ff5b13a80b965a12b7d2dc51ee50a5d6819d1f60" +dependencies = [ + "anyhow", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "soup3" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ccd1f4aee0854a16b0b489ba843798e2eb4cdcddd4a61248f7db9ce8b6df1" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8869997193d52a61a1db48627bdaa57343f76e2c5132ee6d351245a6ab30631e" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[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.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c81f13d9a334a6c242465140bd262fae382b752ff2011c4f7419919a9c97922" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.1.0", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +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.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.20", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.18", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracker" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5c98457ff700aaeefcd4a4a492096e78a2af1dd8523c66e94a3adb0fdbd415" +dependencies = [ + "tracker-macros", +] + +[[package]] +name = "tracker-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc19eb2373ccf3d1999967c26c3d44534ff71ae5d8b9dacf78f4b13132229e48" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit6" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c359ef247305dcade3363c281c505b943e0e6162a42eac76ff76ed8e7cebfbd" +dependencies = [ + "gdk4", + "gio", + "glib", + "gtk4", + "javascriptcore6", + "libc", + "soup3", + "webkit6-sys", +] + +[[package]] +name = "webkit6-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96284c5280af5984dbdae8dae3cfeea11b44b214f9bd42b35c0ca75903bccce2" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "javascriptcore6-sys", + "libc", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-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-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", + "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_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_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_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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[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_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_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_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 = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "xdg-home" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..40fd270 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +resolver = "2" + +members = [ + "libpaket", + "advices", +] + + +[workspace.package] +authors = ["Jane Rachinger "] +edition = "2021" +license = "AGPL-3.0-only" +version = "0.1.0" diff --git a/libpaket/Cargo.toml b/libpaket/Cargo.toml new file mode 100644 index 0000000..0f7291f --- /dev/null +++ b/libpaket/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "libpaket" +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +aes-gcm = { version = "0.10.3", optional = true } +ed25519-dalek = { version = "2.1.0", optional = true } +hmac = { version = "0.12.1", optional = true } +num_enum = { version = "0.7", optional = true } + +# TODO: Consolidate? +rand = "0.8.5" +random-string = "1.1.0" +reqwest = { version = "0.12", features = ["json", "cookies", "gzip", "http2"] } +secrecy = { version = "0.8.0", features = ["serde"] } +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" +serde_repr = { version = "0.1.18", optional = true } +url = "2.5.0" +base64 = "0.22" + +# TODO: consider splitting login.rs refresh_token and authorization_token +# (sha2 and urlencoding only used with authorization_token) +# sha2 also used in briefankuendigung and packstation_register_regtoken +sha2 = "0.10.8" +urlencoding = "2.1.3" + +uuid = { version = "1.7.0", features = ["v4", "serde"], optional = true } +serde_newtype = "0.1.1" +thiserror = "1.0.56" + +[features] +default = [ + "advices", + "locker_all" +] + +advices = [ + #"dep:sha2", + "dep:uuid", + "dep:aes-gcm", +] + +locker_all = [ + "locker_register_all", + "locker_ble", +] + +locker_base = [ + "dep:uuid", + "dep:serde_repr", + "dep:ed25519-dalek", +] + +locker_ble = [ + "locker_base", + "dep:num_enum", +] + +locker_register_all = [ + "locker_register_regtoken", +] + +locker_register_base = [ + "locker_base", + "dep:hmac", + #"dep:sha2", + +] + +locker_register_regtoken = [ + "locker_register_base" +] \ No newline at end of file diff --git a/libpaket/README.md b/libpaket/README.md new file mode 100644 index 0000000..526059f --- /dev/null +++ b/libpaket/README.md @@ -0,0 +1,31 @@ +# libpaket + +This is an unofficial client to various DHL APIs, more specific the ones that the proprietary app `Post & DHL` uses. + +## Features + +- Mail Notification (Briefankündigung) + +## Goals + +- app-driven parcel lockers (App-gesteuerte Packstation) + +## Examples + +In the examples error-handling is ignored for simplicity. You don’t want to do that. + +### Getting mail notifications (Briefankündigung) +```rust +// Requires a logged-in user. +let token: libpaket::login::DHLIdToken; + +let response = libpaket::WebClient::new().advices(&token).await.unwrap(); + +let client = libpaket::AdviceClient::new(); + +if let Some(current_advice) = response.get_current_advice() { + let advices_token = client.access_token(&response),unwrap(); + let bin: Vec = client.fetch_advice_image(¤t_advice.list[0], advice_token).await.unwrap(); +} +``` + diff --git a/libpaket/src/advices/briefankuendigung.rs b/libpaket/src/advices/briefankuendigung.rs new file mode 100644 index 0000000..f4e3788 --- /dev/null +++ b/libpaket/src/advices/briefankuendigung.rs @@ -0,0 +1,154 @@ +pub use super::Advice; +use super::AdvicesResponse; + +use reqwest::header::HeaderMap; +use serde::Serialize; +use crate::constants::webview_user_agent; +use crate::LibraryResult; + +pub struct AdviceClient { + client: reqwest::Client, +} + +#[derive(Debug, Clone)] +pub struct UatToken(String); + +impl AdviceClient { + pub fn new() -> Self { + Self { + client: reqwest::ClientBuilder::new() + .default_headers(headers()) + .user_agent(webview_user_agent()) + .build() + .unwrap(), + } + } + + pub async fn access_token<'t>(&self, advices: &AdvicesResponse) -> LibraryResult { + mini_assert_inval!(advices.has_any_advices()); + + mini_assert_api_eq!(advices.access_token_url.as_ref().unwrap().as_str(), endpoint_access_tokens()); + + let req = self + .client + .post(endpoint_access_tokens()) + .json(&GrantToken { + grant_token: advices.grant_token.as_ref().unwrap().clone(), + }) + .build() + .unwrap(); + + let res = self.client.execute(req).await; + + if let Err(err) = res { + return Err(err.into()); + } + let res = res.unwrap(); + + for cookie in res.cookies() { + if cookie.name() == "UAT" { + return Ok(UatToken(cookie.value().to_string())); + } + } + // FIXME: Parse errors here better (checking if we're unauthorized,...) + panic!("NO UAT Token in access_token"); + } + + pub async fn fetch_advice_image( + &self, + advice: &Advice, + uat: &UatToken, + ) -> LibraryResult> { + println!("URL: {}", &advice.image_url); + + // get key to "deobfuscate" the data + // match behaviour from javascript + let uuid = { + let uuid_str = advice.image_url.split("/").last().unwrap(); + let uuid_vec = uuid_str.split("?").collect::>(); + *uuid_vec.first().unwrap() + }; + + let (secret_key, iv, aad) = { + use sha2::{Digest, Sha512}; + let hash = { + let mut hasher = Sha512::new(); + hasher.update(uuid.as_bytes()); + hasher.finalize() + }; + //secretKey: e.subarray(0, 32), + //iv: e.subarray(32, 44), + //aad: e.subarray(44, 56) + ( + hash[0..32].to_vec(), + hash[32..44].to_vec(), + hash[44..56].to_vec(), + ) + }; + + let req = self.client + .get(&advice.image_url) + .header("Cookie", format!("UAT={}", uat.0)) + .build() + .unwrap(); + let res = self.client.execute(req).await; + if let Err(err) = res { + return Err(err.into()); + } + let res = res.unwrap(); + + let bytes = res.bytes().await.unwrap(); + + let bytes = { + use aes_gcm::{ + aead::Payload, + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce, + }; + let aes = Aes256Gcm::new_from_slice(&secret_key[..]).unwrap(); + let nonce = Nonce::from_slice(&iv[..]); + let payload = Payload { + msg: &bytes[..], + aad: &aad[..], + }; + aes.decrypt(nonce, payload).unwrap() + }; + + Ok(bytes) + } +} + +#[derive(Serialize)] +struct GrantToken { + grant_token: String, +} + +fn headers() -> HeaderMap { + let aaa = vec![ + ("Host", "briefankuendigung.dhl.de"), + ("Connection", "keep-alive"), + ("Connect-Type", "application/json"), + ("Pragma", "no-cache"), + ("Cache-Control", "no-cache,no-store,must-revalidate"), + ("Expires", "0"), + ("Accept", "*/*"), + ("Origin", "https://www.dhl.de"), + ("X-Requested-With", "de.dhl.paket"), + ("Sec-Fetch-Site", "same-site"), + ("Sec-Fetch-Mode", "cors"), + ("Sec-Fetch-Dest", "empty"), + ("Referer", "https://www.dhl.de/"), + ("Accept-Language", "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"), + ]; + + let mut map = HeaderMap::new(); + for bbb in aaa { + map.append(bbb.0, bbb.1.parse().unwrap()); + } + + map +} + +pub fn endpoint_access_tokens() -> &'static str { + "https://briefankuendigung.dhl.de/pdapp-web/access-tokens" +} diff --git a/libpaket/src/advices/mod.rs b/libpaket/src/advices/mod.rs new file mode 100644 index 0000000..f5c4494 --- /dev/null +++ b/libpaket/src/advices/mod.rs @@ -0,0 +1,5 @@ +mod www; +mod briefankuendigung; + +pub use www::*; +pub use briefankuendigung::*; \ No newline at end of file diff --git a/libpaket/src/advices/www.rs b/libpaket/src/advices/www.rs new file mode 100644 index 0000000..db4bf2e --- /dev/null +++ b/libpaket/src/advices/www.rs @@ -0,0 +1,113 @@ +use serde::Deserialize; +use serde_newtype::newtype; + +use crate::utils::{as_url, CookieHeaderValueBuilder}; + +#[derive(Deserialize, Debug, Clone)] +pub struct Advice { + pub campaign_link_text: Option, + pub campaign_url: Option, + pub image_url: String, + pub thumbnail_url: String, +} + +#[derive(Deserialize, Debug)] +pub struct AdvicesList { + #[serde(rename = "advices")] + pub list: Vec, + pub date: String, +} + +newtype! { + #[derive(Debug)] + pub AdviceAccessTokenUrl: String[ + |string| { + let res = url::Url::try_from(string.as_str()); + res.is_ok() + }, "" + ] = "".to_string(); +} + +#[derive(Deserialize, Debug)] +pub struct AdvicesResponse { + // access_token_url, basic_auth, grant_token is null if no advices are available + #[serde(rename = "accessTokenUrl")] + pub(super) access_token_url: Option, + #[serde(rename = "basicAuth")] + pub(super) basic_auth: Option, + #[serde(rename = "currentAdvice")] + current_advice: Option, + #[serde(rename = "grantToken")] + pub(super) grant_token: Option, + + #[serde(rename = "oldAdvices")] + old_advices: Vec, +} + +impl AdvicesResponse { + pub fn has_any_advices(&self) -> bool { + self.has_current_advice() || self.has_old_advices() + } + + pub fn has_current_advice(&self) -> bool { + self.current_advice.is_some() + } + + pub fn has_old_advices(&self) -> bool { + self.old_advices.len() > 0 + } + + pub fn get_current_advice(&self) -> Option<&AdvicesList> { + self.current_advice.as_ref() + } + + pub fn get_old_advices(&self) -> &Vec { + self.old_advices.as_ref() + } +} + +fn endpoint_advices() -> url::Url { + as_url("https://www.dhl.de/int-aviseanzeigen/advices?width=400") +} + +impl crate::www::WebClient { + // FIXME: more error parsing + pub async fn advices(&self, dhli: &crate::login::DHLIdToken) -> crate::LibraryResult { + let cookie_headervalue = CookieHeaderValueBuilder::new() + .add_dhli(dhli) + .add_dhlcs(dhli) + .build_string(); + + let req = self.web_client + .get(endpoint_advices().clone()) + .header("Cookie", cookie_headervalue) + .build() + .unwrap(); + + let res = self.web_client.execute(req).await; + + if let Err(err) = res { + return Err(err.into()) + } + + let res = res.unwrap(); + let res_text = res.text().await.unwrap(); + let res = serde_json::from_str::(&res_text); + + let res = match res { + Ok(res) => res, + Err(err) => { + return Err(err.into()) + } + }; + + if res.access_token_url.is_some() { + if res.access_token_url.as_ref().unwrap().as_str() != crate::advices::endpoint_access_tokens() { + return Err(crate::LibraryError::APIChange); + } + } + + Ok(res) + } + +} diff --git a/libpaket/src/common.rs b/libpaket/src/common.rs new file mode 100644 index 0000000..608bd7a --- /dev/null +++ b/libpaket/src/common.rs @@ -0,0 +1,35 @@ +use crate::{LibraryError, LibraryResult}; +use serde::Deserialize; + +#[derive(serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum APIErrorType { + InvalidGrant, +} + +#[derive(Deserialize)] +pub(crate) struct APIError { + pub error: APIErrorType, + pub error_description: String, +} + +// mostly used in the aggregated endpoint +#[derive(serde::Deserialize)] +#[serde(untagged)] +pub(crate) enum APIResult { + APIError(APIError), + Okay(T), +} + +impl Into> for APIResult { + fn into(self) -> LibraryResult { + match self { + APIResult::APIError(err) => { + return Err(LibraryError::from(err)); + } + APIResult::Okay(res) => { + return Ok(res); + } + } + } +} diff --git a/libpaket/src/constants.rs b/libpaket/src/constants.rs new file mode 100644 index 0000000..cc8f476 --- /dev/null +++ b/libpaket/src/constants.rs @@ -0,0 +1,21 @@ + +pub fn app_version() -> &'static str { + "9.9.1.95 (a72fec7be)" +} + +pub fn linux_android_version() -> &'static str { + "Linux; Android 11" +} + +pub fn webview_user_agent() -> String { + format!("{} [DHL Paket Android App/{}]", web_user_agent(), app_version()) +} + +pub fn device_string() -> &'static str { + "OnePlus 6T Build/RQ3A.211001.001" +} + +pub fn web_user_agent() -> String { + format!("Mozilla/5.0 ({}; {}; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/117.0.0.0 Mobile Safari/537.36", + linux_android_version(), device_string()) +} \ No newline at end of file diff --git a/libpaket/src/lib.rs b/libpaket/src/lib.rs new file mode 100644 index 0000000..3de0a18 --- /dev/null +++ b/libpaket/src/lib.rs @@ -0,0 +1,66 @@ +mod www; +pub use www::WebClient; + +pub mod login; +pub use login::OpenIdClient; + +pub mod stammdaten; +pub use stammdaten::StammdatenClient; + +mod common; +#[macro_use] +mod utils; +pub mod constants; + +#[cfg(feature = "locker_base")] +pub mod locker; + +#[cfg(feature = "advices")] +pub mod advices; +#[cfg(feature = "advices")] +pub use advices::AdviceClient; + +/*#[cfg(test)] +pub(crate) mod private;*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum LibraryError { + #[error("network error: unable to fetch resource")] + NetworkFetch, + #[error("invalid credentials, unauthorized")] + Unauthorized, + #[error("invalid argument: {0}")] + InvalidArgument(String), + #[error("internal error, unable to decode: {0}")] + DecodeError(String), + #[error("upstream api was changed. not continuing")] + APIChange, +} + +pub type LibraryResult = Result; + +impl From for LibraryError { + fn from(item: reqwest::Error) -> Self { + if item.is_timeout() || item.is_request() || item.is_connect() || item.is_decode() { + Self::NetworkFetch + } else { + panic!("FIXME: unknown reqwest error kind: {:?}", item) + } + } +} + +impl From for LibraryError { + fn from(value: common::APIError) -> Self { + match value.error { + common::APIErrorType::InvalidGrant => Self::Unauthorized + } + } +} + +impl From for LibraryError { + fn from(value: serde_json::Error) -> Self { + Self::DecodeError(value.to_string()) + } +} \ No newline at end of file diff --git a/libpaket/src/locker/api.rs b/libpaket/src/locker/api.rs new file mode 100644 index 0000000..f730f8c --- /dev/null +++ b/libpaket/src/locker/api.rs @@ -0,0 +1,40 @@ +use reqwest::{header::HeaderMap, Request, RequestBuilder}; + +use crate::constants::{app_version, web_user_agent}; + +pub struct Client { + client: reqwest::Client, +} + +impl Client { + pub fn new() -> Self { + Client { + client: reqwest::ClientBuilder::new() + .default_headers(headers()) + .user_agent(user_agent()) + .build() + .unwrap(), + } + } +} + +fn user_agent() -> String { + format!("LPS Consumer SDK/2.1.0 okhttp/4.9.1 {}", web_user_agent()) +} + +fn headers() -> HeaderMap { + let aaa = vec![ + /* ("accept", "application/json") */ + ("app-version", app_version()), + ("device-os", "Android"), + ("device-key", "") /* is the android id... */ + + ]; + + let mut map = HeaderMap::new(); + for bbb in aaa { + map.append(bbb.0, bbb.1.parse().unwrap()); + } + + map +} \ No newline at end of file diff --git a/libpaket/src/locker/command.rs b/libpaket/src/locker/command.rs new file mode 100644 index 0000000..7817b41 --- /dev/null +++ b/libpaket/src/locker/command.rs @@ -0,0 +1,330 @@ +use num_enum::TryFromPrimitive; +use uuid::Uuid; + +use crate::{LibraryError, LibraryResult}; + +use super::LockerServiceUUID; + +#[derive(Clone, Copy, TryFromPrimitive, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] +pub enum CommandType { + InitSessionRequest = 1, + InitSessionResponse = 2, + OpenRequest = 3, + OpenedRespone = 4, + AlreadyOpenResponse = 5, + OpenAutoignoreRequest = 6, + CloseCompartmentsResponse = 7, + PingRequest = 8, + CheckFirmwareVersionRequest = 9, + CheckFirmwareVersionResponse = 10, + UpdateFirmwareRequest = 11, + UpdateFirmwareResponse = 12, + GetLogCounterRequest = 13, + GetLogCounterResponse = 14, + GetLogFileRequest = 15, + GetLogFileResponse = 16, + CheckOpenedComparmentsRequest = 17, + CheckOpenedCompartmentsResponse = 18, + TransferFirmwareRequest = 19, + TransferFirmwareResponse = 20, +} + +const REQUESTS: [CommandType; 10] = [ + CommandType::InitSessionRequest, + CommandType::OpenRequest, + CommandType::OpenAutoignoreRequest, + CommandType::PingRequest, + CommandType::CheckFirmwareVersionRequest, + CommandType::UpdateFirmwareRequest, + CommandType::GetLogCounterRequest, + CommandType::GetLogFileRequest, + CommandType::CheckOpenedComparmentsRequest, + CommandType::TransferFirmwareRequest, +]; + +struct ResponseInitSession { + raw_command: Command +} + +enum Response { + InitSession(ResponseInitSession), +} + +impl TryFrom for Response { + type Error = LibraryError; + + fn try_from(value: Command) -> Result { + if REQUESTS.binary_search(&value.r#type).is_ok() { + return Err(LibraryError::InvalidArgument("TryFrom for Response: CommandType is a Request (expected Response)".to_string())) + } + todo!() + } +} + +// Message( +// command (byte) +// length in bytes (int) +// payload (arr) +// initVector (arr) +// metadata (arr) +// checksum (short) +// + +// Checksum function + + +pub struct Command { + r#type: CommandType, + payload: Vec, + init_vector: Vec, + metadata: Vec +} + +struct PrimitiveBuilder { + bin: Vec, +} + +impl PrimitiveBuilder { + fn new() -> Self { + PrimitiveBuilder { + bin: Vec::new() + } + } + + fn write_u8(self, number: u8) -> Self { + self.write_array(&[number]) + } + + fn write_u16(self, number: u16) -> Self { + self.write_array(&number.to_be_bytes()) + } + + fn write_u32(self, number: u32) -> Self { + self.write_array(&number.to_be_bytes()) + } + + fn write_array(mut self, new: &[u8]) -> Self { + for b in new { + self.bin.push(*b); + } + + self + } + + fn write_array_with_len(self, bin: &[u8]) -> Self { + self + .write_u32(bin.len() as u32) + .write_array(bin) + } + + fn finish(self) -> Vec { + self.bin + } +} + +struct PrimitiveReader<'a> { + offset: usize, + vec: &'a [u8], +} + +// Yes, I know Cursor exists and eio exists. from_be_bytes should be a stdlib trait tho. +impl<'a> PrimitiveReader<'a> { + fn read_u8(&mut self) -> u8 { + let number = self.vec[self.offset]; + self.offset += 1; + number + } + + fn read_u16(&mut self) -> u16 { + let arr: [u8; 2] = self.read_arr_const(); + u16::from_be_bytes(arr) + } + + fn read_u32(&mut self) -> u32 { + let arr: [u8; 4] = self.read_arr_const(); + u32::from_be_bytes(arr) + } + + fn read_u64(&mut self) -> u64 { + let arr: [u8; 8] = self.read_arr_const(); + u64::from_be_bytes(arr) + } + + fn read_arr_const(&mut self) -> [u8; N] { + let mut arr = [0u8; N]; + for n in 0..N { + arr[n] = self.read_u8(); + } + + return arr; + } + + fn read_arr(&mut self, n: usize) -> Vec { + let mut arr: Vec = vec![]; + for _ in 0..n { + arr.push(self.read_u8()); + } + + arr + } + + fn read_arr_from_len(&mut self) -> Vec { + let size = self.read_u32() as usize; + self.read_arr(size) + } + + fn left_to_process(&self) -> usize { + self.vec.len() - self.offset + } +} + +impl Command { + fn checksum(bin: &[u8]) -> u16 { + // CRC16 of some kind... + // i'm too eepy to use the crc crate for this + let mut result: u16 = 0; + + for byte in bin { + result = result ^ ((*byte as u16) << 8); + let mut i = 0; + while i < 8 { + let mut a = result << 1; + if result & 0x8000 != 0 { + a ^= 0x1021; + } + result = a; + i += 1; + } + } + result + } + + pub fn parse(bin: Vec) -> LibraryResult { + // command byte + message length + 3 empty message arguments (array with size 0) + 2 checksum bytes + let to_few_bytes = LibraryError::InvalidArgument("Command::parse: Invalid vec.len() (to few bytes)".to_string()); + if bin.len() < 1 + 4 + (4 * 3) + 2 { + return Err(to_few_bytes) + } + + { + let checksum = { + PrimitiveReader { + offset: bin.len() - 2, + vec: bin.as_slice(), + }.read_u16() + }; + + if checksum != Self::checksum(&bin[0..bin.len() - 2]) { + return Err(LibraryError::InvalidArgument("Command::parse: Invalid checksum".to_string())); + } + } + + let mut reader = PrimitiveReader { + offset: 0, + vec: bin.as_slice(), + }; + + let r#type = reader.read_u8(); + let Ok(r#type) = CommandType::try_from_primitive(r#type) else { + return Err(LibraryError::DecodeError("unable determine CommandType".to_string())); + }; + + let size_of_message = reader.read_u32() as usize; + if reader.left_to_process() < size_of_message { + return Err(to_few_bytes); + } + + let payload: Vec = reader.read_arr_from_len(); + let init_vector = reader.read_arr_from_len(); + let metadata = reader.read_arr_from_len(); + + Ok(Command { + r#type, + payload: payload.clone(), + init_vector: init_vector.clone(), + metadata: metadata.clone(), + }) + } + + pub fn finish(self) -> Vec { + let vec1 = PrimitiveBuilder::new() + .write_array_with_len(&self.payload) + .write_array_with_len(&self.init_vector) + .write_array_with_len(&self.metadata) + .finish(); + + let vec2 = PrimitiveBuilder::new() + .write_u8(self.r#type as u8) + .write_u32(vec1.len() as u32 + 2) + .write_array(&vec1) + .finish(); + + PrimitiveBuilder::new() + .write_array(&vec2) + .write_u16(Self::checksum(vec2.as_slice())) + .finish() + } + + fn assert_only_decimal(str: &String) { + for c in str.chars() { + assert!(c.is_digit(10)); + } + } + + pub fn init_session_request(uuid: LockerServiceUUID) -> LibraryResult { + let uuid: Uuid = uuid.into(); + let fields = uuid.as_fields(); + // For some reason the code make sure that the heximal string only contains digits from base10... + // Interesting findings: first 5 digits are PLZ, last three packstation id (end-user), then uid? + + // de.dhl.paket does some kinky string conversion + + let vec = Vec::::new().into_iter() + .chain(fields.0.to_be_bytes().into_iter()) + .chain(fields.1.to_be_bytes().into_iter()) + .chain(fields.2.to_be_bytes().into_iter()) + .chain(fields.3.to_vec().into_iter()) + .collect::>(); + + let new_vec = PrimitiveBuilder::new() + .write_array_with_len(&vec).finish(); + + Ok(Command { + r#type: CommandType::InitSessionRequest, + payload: Vec::new(), + init_vector: Vec::new(), + metadata: new_vec, + }) + } +} + +/*#[cfg(test)] +mod test { + use crate::private::sample_packstation::{INIT_SESSION_REQUEST, INIT_SESSION_RESPONSE, INIT_SESSION_SERVICE_UUID}; + + use super::{Command, CommandType}; + + #[test] + fn command_init_request() { + let mut corrected = Vec::::new(); + for b in INIT_SESSION_REQUEST { + corrected.push(b as u8); + } + + let command = Command::init_session_request(uuid::uuid!(INIT_SESSION_SERVICE_UUID).try_into().unwrap()).unwrap(); + + assert_eq!(command.finish().as_slice(), corrected.as_slice()) + } + + #[test] + fn command_init_session_response() { + let mut corrected = Vec::::new(); + for b in INIT_SESSION_RESPONSE { + corrected.push(b as u8); + } + + let command = Command::parse(corrected); + } + +}*/ \ No newline at end of file diff --git a/libpaket/src/locker/crypto.rs b/libpaket/src/locker/crypto.rs new file mode 100644 index 0000000..abb0688 --- /dev/null +++ b/libpaket/src/locker/crypto.rs @@ -0,0 +1,75 @@ +use base64::{engine::general_purpose, Engine as _}; +use rand::prelude::*; +use uuid::Uuid; +use ed25519_dalek::SigningKey; +use ed25519_dalek::Signer; + + +pub struct CustomerKeySeed { + postnumber: String, + seed: Seed, + uuid: Uuid, +} + +pub struct Seed { + bytes: Vec, +} + +impl Seed { + pub(self) fn from_bytes(bytes: Vec) -> Self { + assert_eq!(bytes.len(), 32); + Seed { + bytes: bytes + } + } + + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + + let mut bytes: Vec = vec![0; 32]; + + rng.fill_bytes(bytes.as_mut_slice()); + + Seed { bytes: bytes } + } + + pub fn as_bytes(&self) -> &[u8; 32] { + (&self.bytes[..]).try_into().unwrap() + } +} + +impl CustomerKeySeed { + pub fn new(postnumber: String) -> Self { + CustomerKeySeed { + postnumber, + seed: Seed::random(), + uuid: uuid::Uuid::new_v4(), + } + } + + pub fn from(postnumber: &String, seed: Vec, uuid: &Uuid) -> Self { + CustomerKeySeed { + postnumber: postnumber.clone(), + seed: Seed::from_bytes(seed), + uuid: uuid.clone() + } + } + + pub(crate) fn sign(&self, message: &[u8]) -> String { + let signing_key = SigningKey::from_bytes(self.seed.as_bytes()); + + let signature = signing_key.sign(message); + + let sig_str = general_purpose::STANDARD.encode(signature.to_bytes()); + + format!("{}.{}", self.uuid.to_string(), sig_str) + } + + pub(crate) fn get_public_key(&self) -> String { + let signing_key = SigningKey::from_bytes(self.seed.as_bytes()); + let public_bytes = signing_key.verifying_key().to_bytes(); + let public_str = general_purpose::STANDARD.encode(public_bytes); + + format!("{}.{}", self.uuid.to_string(), public_str) + } +} diff --git a/libpaket/src/locker/mod.rs b/libpaket/src/locker/mod.rs new file mode 100644 index 0000000..eee28ae --- /dev/null +++ b/libpaket/src/locker/mod.rs @@ -0,0 +1,23 @@ +#[cfg(feature = "locker_ble")] +mod command; +#[cfg(feature = "locker_ble")] +mod types; +#[cfg(feature = "locker_ble")] +pub use types::*; + +mod api; +pub mod crypto; + +#[cfg(feature = "locker_register_base")] +mod register_base; +#[cfg(feature = "locker_register_regtoken")] +mod register_regtoken; +#[cfg(feature = "locker_register_regtoken")] +mod regtoken; +#[cfg(feature = "locker_register_base")] +pub mod register { + pub use super::register_base::*; + + #[cfg(feature = "locker_register_regtoken")] + pub use super::regtoken::*; +} diff --git a/libpaket/src/locker/register_base.rs b/libpaket/src/locker/register_base.rs new file mode 100644 index 0000000..fcf6146 --- /dev/null +++ b/libpaket/src/locker/register_base.rs @@ -0,0 +1,113 @@ +use serde::Deserialize; +use serde::Serialize; +use serde_repr::Deserialize_repr; + +use crate::common::APIResult; +use crate::login::DHLIdToken; +use crate::LibraryResult; + +#[derive(Deserialize)] +pub struct RegistrationPayload { + pub(crate) challenge: String, + #[serde(rename = "registrationOptions")] + pub registration_options: Vec, +} + +#[derive(Deserialize, PartialEq, Eq)] +pub enum RegistrationOption { + #[serde(rename = "registerDirect")] + Direct, + #[serde(rename = "registerByRegToken")] + ByRegToken, + #[serde(rename = "registerByPeerDevice")] + ByPeerDevice, + #[serde(rename = "registerByVerifyToken")] + ByVerifyToken, + #[serde(rename = "registerByCardNumber")] + ByCardNumber, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct DeviceMetadata { + pub manufacturer_model: String, + pub manufacturer_name: String, + pub manufacturer_operating_system: String, + pub name: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Device { + #[serde(flatten)] + pub metadata: DeviceMetadata, + + pub id: String, + pub registered_at: String, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum DeviceRegistrationResponse { + Error { + id: APIRegisterError, + description: String, + }, + Okay(Device), +} + +#[derive(Deserialize_repr, PartialEq, Debug)] +#[repr(u8)] +pub enum APIRegisterError { + MaxDevices = 7, + ScanInvalidCustomerCard = 14, + AlreadyRegisteredOneDevice = 16, + ScanInvalidRegToken = 17, + ScanExpiredAuthToken = 19, + ScanInvalidAuthToken = 20, + UserCanNotYetRegister = 21, +} + + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(super) struct RegistrationCommonDevice { + #[serde(flatten)] + pub(super) metadata: DeviceMetadata, + + pub(super) public_key: String, +} + +impl crate::StammdatenClient { + pub async fn begin_registration( + &self, + dhli: &DHLIdToken, + ) -> LibraryResult { + let body = "{\"hasVerifyToken\": false}"; + + let req = self.base_request( + self.client + .put(endpoint_devices_begin_registration()) + .body(body) + .header("content-length", body.len()) + .header("content-type", "application/json; charset=UTF-8"), + dhli, + ); + let res = self.client.execute(req).await; + if let Err(err) = res { + return Err(err.into()); + }; + + let res = res.unwrap().text().await.unwrap(); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => res.into(), + Err(err) => Err(err.into()), + } + } +} + +fn endpoint_devices_begin_registration() -> &'static str { + "https://www.dhl.de/int-stammdaten/public/devices/beginRegistration" +} diff --git a/libpaket/src/locker/register_regtoken.rs b/libpaket/src/locker/register_regtoken.rs new file mode 100644 index 0000000..28f6419 --- /dev/null +++ b/libpaket/src/locker/register_regtoken.rs @@ -0,0 +1,152 @@ +use serde::{Deserialize, Serialize}; + +use hmac::{digest::CtOutput, Mac, SimpleHmac}; +use sha2::Sha256; + +use crate::common::APIResult; +use crate::locker::register::{DeviceMetadata, DeviceRegistrationResponse, RegToken, RegistrationCommonDevice, RegistrationOption, RegistrationPayload}; +use crate::locker::crypto::CustomerKeySeed; +use crate::login::DHLIdToken; +use crate::LibraryResult; + +use base64::{engine::general_purpose, Engine as _}; + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct RegistrationRegToken { + pub(super) challenge: String, + pub(super) customer_password: String, + pub(super) device: RegistrationCommonDevice, + pub(super) verifier: String, + pub(super) verifier_signature: String, +} + +impl RegistrationRegToken { + pub fn new( + customer_key_seed: &CustomerKeySeed, + begin_registration: RegistrationPayload, + reg_token: &RegToken, + device_metadata: DeviceMetadata, + ) -> Self { + let challange_bin = general_purpose::STANDARD.decode(begin_registration.challenge.as_str()).unwrap(); + + let mut mac = reg_token.hmac(); + mac.update(challange_bin.as_slice()); + + let verifier_bin: CtOutput> = mac.finalize(); + let verifier_bin = verifier_bin.into_bytes(); + let verifier = general_purpose::STANDARD.encode(&verifier_bin); + let verifier_signature = customer_key_seed.sign(&verifier_bin); + + Self { + challenge: begin_registration.challenge, + customer_password: reg_token.customer_password(), + device: RegistrationCommonDevice { + metadata: device_metadata, + public_key: customer_key_seed.get_public_key(), + }, + verifier: verifier, + verifier_signature: verifier_signature, + } + } +} + +impl crate::StammdatenClient { + + pub async fn register_by_regtoken( + &self, + dhli: &DHLIdToken, + customer_key_seed: &CustomerKeySeed, + registration_payload: RegistrationPayload, + device_metadata: DeviceMetadata, + reg_token: &crate::locker::register::RegToken, + ) -> LibraryResult { + + let mut valid = false; + for option in registration_payload.registration_options.iter() { + if *option == RegistrationOption::ByRegToken { + valid = true; + } + } + assert!(valid); + + let body = RegistrationRegToken::new( + customer_key_seed, + registration_payload, + reg_token, + device_metadata + ); + + let body = serde_json::to_string(&body).unwrap(); + + let req = self.base_request( + self.client + .post(endpoint_devices_register_by_regtoken()) + .header("content-length", body.len()) + .body(body) + .header("content-type", "application/json; charset=UTF-8"), + dhli, + ); + + let res = self.client.execute(req).await; + if let Err(err) = res { + return Err(err.into()); + }; + + let res = res.unwrap().text().await.unwrap(); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => res.into(), + Err(err) => Err(err.into()), + } + } +} + + +fn endpoint_devices_register_by_regtoken() -> &'static str { + "https://www.dhl.de/int-stammdaten/public/devices/registerByRegToken" +} + + +/*#[cfg(test)] +mod registration_api { + use std::str::FromStr as _; + use base64::Engine as _; + + use crate::private::init as private; + + #[test] + fn regtoken_customer_password() { + let regtoken = super::RegToken::parse_from_qrcode_uri(&private::IN_REGTOKEN).unwrap(); + assert!(regtoken.customer_password() == private::OUT_CUSTOMER_PASSWORD); + + } + + #[test] + fn register_via_regtoken(){ + let regtoken = super::RegToken::parse_from_qrcode_uri(&private::IN_REGTOKEN).unwrap(); + + let customer_key_seed = crate::locker::crypto::CustomerKeySeed::from(&private::IN_POSTNUMBER.to_string(), + base64::engine::general_purpose::STANDARD.decode(&private::IN_SEED_BASE64).unwrap(), + &uuid::Uuid::from_str(&private::IN_KEY_ID).unwrap()); + + let registration_payload = super::RegistrationPayload { + challenge: private::IN_CHALLENGE.to_string(), + registration_options: Vec::new(), + }; + + let reg_reg_token = super::RegistrationRegToken::new( + &customer_key_seed, + registration_payload, + ®token, + super::DeviceMetadata { manufacturer_model: "".to_string(), manufacturer_name: "".to_string(), manufacturer_operating_system: "".to_string(), name: "".to_string() } + ); + + assert!(reg_reg_token.verifier == private::OUT_VERIFIER); + assert!(reg_reg_token.verifier_signature == private::OUT_VERIFIERSIGNATURE); + assert!(reg_reg_token.device.public_key == private::OUT_DEVICE_PUBKEY); + } + +} +*/ \ No newline at end of file diff --git a/libpaket/src/locker/regtoken.rs b/libpaket/src/locker/regtoken.rs new file mode 100644 index 0000000..a79e25c --- /dev/null +++ b/libpaket/src/locker/regtoken.rs @@ -0,0 +1,48 @@ +use base64::{engine::general_purpose, Engine as _}; +use hmac::{Mac, SimpleHmac}; +use sha2::Sha256; + +use crate::{LibraryResult, LibraryError}; + +pub struct RegToken { + token: Vec, +} + +impl RegToken { + pub fn parse_from_qrcode_uri(uri: &str) -> LibraryResult { + if uri.len() <= 23 { + return Err(LibraryError::DecodeError("RegTokenUri too short".to_string())); + } + if !uri.starts_with("urn:dhl.de:regtoken:v1:") { + return Err(LibraryError::DecodeError("RegTokenUri has invalid magic".to_string())); + } + + let token = &uri[23..]; + let token = general_purpose::STANDARD.decode(token); + + let Ok(mut token) = token else { + return Err(LibraryError::DecodeError("RegTokenUri not decodeable (base64)".to_string())); + }; + + if token.len() > 64 { + return Err(LibraryError::DecodeError("RegToken longer than expected".to_string())); + } + + if token.len() < 32 { + token.extend(vec![0; 32 - token.len()].iter()) + } + + Ok(RegToken { token }) + } + + pub fn customer_password(&self) -> String { + general_purpose::STANDARD.encode(&self.token[32..]) + } + + pub fn hmac(&self) -> SimpleHmac { + let mac = SimpleHmac::::new_from_slice(&self.token[0..32]) + .expect("HMAC can take key of any size"); + + mac + } +} diff --git a/libpaket/src/locker/types.rs b/libpaket/src/locker/types.rs new file mode 100644 index 0000000..7667cfc --- /dev/null +++ b/libpaket/src/locker/types.rs @@ -0,0 +1,29 @@ +use crate::LibraryError; + +// 601e7028-0565- +pub static LOCKER_SERVICE_UUID_PREFIX: (u32, u16) = (0x601e7028, 0x0565); + +pub struct LockerServiceUUID { + service_uuid: uuid::Uuid, +} + +impl TryFrom for LockerServiceUUID { + type Error = crate::LibraryError; + + fn try_from(value: uuid::Uuid) -> Result { + let fields = value.as_fields(); + if fields.0 != LOCKER_SERVICE_UUID_PREFIX.0 || fields.1 != LOCKER_SERVICE_UUID_PREFIX.1 { + return Err(LibraryError::InvalidArgument("TryFrom for LockerServiceUUID: prefix mismatch (expected 601e7028-0565-)".to_string())) + } + + Ok(LockerServiceUUID { + service_uuid: value + }) + } +} + +impl Into for LockerServiceUUID { + fn into(self) -> uuid::Uuid { + self.service_uuid + } +} \ No newline at end of file diff --git a/libpaket/src/login/constants.rs b/libpaket/src/login/constants.rs new file mode 100644 index 0000000..3b4549e --- /dev/null +++ b/libpaket/src/login/constants.rs @@ -0,0 +1,130 @@ +use super::utils::CodeVerfier; +use super::dhl_claims::DHLClaimsOptional; +use serde::Serialize; + +pub fn client_id() -> &'static str { + "42ec7de4-e357-4c5d-aa63-f6aae5ca4d8f" +} + +pub fn redirect_uri() -> &'static str { + "dhllogin://de.dhl.paket/login" +} + +pub mod token { + use super::*; + + pub fn refresh_token_form(refresh_token: &str) -> Vec<(&str, &str)> { + vec![ + ("refresh_token", refresh_token), + ("grant_type", "refresh_token"), + ("client_id", client_id()), + ] + } + + pub fn authorization_code_form( + authorization_code: String, + code_verfier: &CodeVerfier, + ) -> Vec<(String, String)> { + vec![ + ("code".to_string(), authorization_code), + ("grant_type".to_string(), "authorization_code".to_string()), + ("redirect_uri".to_string(), redirect_uri().to_string()), + ("code_verifier".to_string(), code_verfier.code_verfier()), + ("client_id".to_string(), client_id().to_string()), + ] + } + + pub fn user_agent() -> &'static str { + "Dalvik/2.1.0 (Linux; U; Android 11; OnePlus 6T Build/RQ3A.211001.001)" + } + + pub fn endpoint() -> &'static str { + "https://login.dhl.de/af5f9bb6-27ad-4af4-9445-008e7a5cddb8/login/token" + } + + + pub fn headers() -> reqwest::header::HeaderMap { + let aaa = vec![ + ("Content-Type", "application/x-www-form-urlencoded"), + ("Accept", "application/json"), + ("Content-Length", "150"), + ("Host", "login.dhl.de"), + ("Connection", "Keep-Alive"), + ("Accept-Encoding", "gzip"), + ]; + + let mut map = reqwest::header::HeaderMap::new(); + for bbb in aaa { + map.append(bbb.0, bbb.1.parse().unwrap()); + } + + map + } +} + +pub mod webbrowser_authorize { + use crate::constants::web_user_agent; + use super::*; + + pub fn user_agent() -> String { + web_user_agent() + } + + pub fn build_endpoint(nonce: &String, code_verfier: &CodeVerfier) -> String { + endpoint().to_string() + authorize_query_string(nonce, code_verfier).as_str() + } + + fn endpoint() -> &'static str { + "https://login.dhl.de/af5f9bb6-27ad-4af4-9445-008e7a5cddb8/login/authorize" + } + + // copying an app state + fn state_json() -> &'static str { + "ewogICJmaWQiOiAiYXBwLWxvZ2luLW1laHItZm9vdGVyIiwKICAiaGlkIjogImFwcC1sb2dpbi1tZWhyLWhlYWRlciIsCiAgIm1yIjogZmFsc2UsCiAgInJwIjogZmFsc2UsCiAgInJzIjogdHJ1ZSwKICAicnYiOiBmYWxzZQp9" + } + + fn authorize_query_string(nonce: &String, code_verfier: &CodeVerfier) -> String { + let mut out = "?".to_string(); + + let mut iter = authorize_query(nonce, code_verfier).into_iter(); + let mut not_first_run = false; + + while let Some(val) = iter.next() { + if not_first_run { + out = out + "&"; + } else { + not_first_run = true; + } + out = out + + urlencoding::encode(&val.0).into_owned().as_str() + + "=" + + urlencoding::encode(&val.1).into_owned().as_str(); + } + + println!("{}", out); + out + } + + #[derive(Serialize, Default)] + struct Claims { + pub id_token: DHLClaimsOptional, + } + + pub fn authorize_query(nonce: &String, code_verfier: &CodeVerfier) -> Vec<(String, String)> { + vec![ + ("redirect_uri".to_string(), redirect_uri().to_string()), + ("client_id".to_string(), client_id().to_string()), + ("response_type".to_string(), "code".to_string()), + ("prompt".to_string(), "login".to_string()), + ("state".to_string(), state_json().to_string()), + ("nonce".to_string(), nonce.clone()), + ("scope".to_string(), "openid offline_access".to_string()), + ("code_challenge".to_string(), code_verfier.code_challenge()), + ("code_challenge_method".to_string(), "S256".to_string()), + ( + "claims".to_string(), + serde_json::to_string(&Claims::default()).unwrap(), + ), + ] + } +} diff --git a/libpaket/src/login/dhl_claims.rs b/libpaket/src/login/dhl_claims.rs new file mode 100644 index 0000000..9057720 --- /dev/null +++ b/libpaket/src/login/dhl_claims.rs @@ -0,0 +1,58 @@ +use super::openid_token::OpenIDToken; + +use serde::{Deserialize, Serialize}; +use serde_newtype::newtype; + +// used to generate scopes, copy of DHLClaims +#[derive(Serialize, Default, Debug)] +pub(super) struct DHLClaimsOptional { + customer_type: Option, + data_confirmation_required: Option, + deactivate_account: Option, + display_name: Option, + email: Option, + last_login: Option, + post_number: Option, + service_mask: Option, + twofa: Option, // probably an enum +} + +// FIXME: made everything optional again, as those weren't initally there +#[derive(Deserialize, Debug, Clone)] +pub struct DHLClaims { + customer_type: u64, + data_confirmation_required: Option, + deactivate_account: Option, + display_name: Option, + email: Option, + last_login: Option, + post_number: PostNumber, + service_mask: Option, + twofa: Option, // FIXME: probably an enum +} + +// TODO: Sanity checks +newtype! { + #[derive(Debug, Clone)] + pub PostNumber: String[|_k| true, + "", + ]; +} +newtype! { + #[derive(Debug)] + pub DHLCs: String[|_k| true, + "", + ]; +} + +impl OpenIDToken { + pub fn get_post_number(&self) -> &PostNumber { + &self.claims.custom.post_number + } + + pub fn get_dhlcs(&self) -> DHLCs { + DHLCs::new(self.claims.sub.clone()).unwrap() + } +} + +pub type DHLIdToken = OpenIDToken; diff --git a/libpaket/src/login/mod.rs b/libpaket/src/login/mod.rs new file mode 100644 index 0000000..757cde7 --- /dev/null +++ b/libpaket/src/login/mod.rs @@ -0,0 +1,100 @@ +pub mod constants; +mod dhl_claims; +mod openid_response; +pub mod openid_token; +mod utils; + +pub use self::dhl_claims::{DHLIdToken, DHLCs}; +pub use self::openid_response::{RefreshToken, TokenResponse}; +pub use self::utils::{CodeVerfier, create_nonce}; + +use super::common::APIResult; +use crate::{LibraryError, LibraryResult}; + + +pub struct OpenIdClient { + client: reqwest::Client, +} + +impl OpenIdClient { + pub fn new() -> Self { + OpenIdClient { + client: reqwest::ClientBuilder::new() + .default_headers(constants::token::headers()) + .user_agent(constants::token::user_agent()) + .build() + .unwrap(), + } + } + + pub async fn token_refresh( + &self, + refresh_token: &RefreshToken, + ) -> LibraryResult { + let req = self + .client + .post(constants::token::endpoint()) + .form(constants::token::refresh_token_form(refresh_token.as_str()).as_slice()) + .build() + .unwrap(); + + let res = self.client.execute(req).await; + + match res { + Ok(res) => { + let res = res.text().await.unwrap(); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => return res.into(), + Err(err) => Err(LibraryError::from(err)), + } + } + Err(err) => Err(LibraryError::from(err)), + } + } + + pub async fn token_authorization( + &self, + authorization_code: String, + code_verfier: &CodeVerfier, + ) -> LibraryResult { + let mut req = self + .client + .post(constants::token::endpoint()) + .form( + constants::token::authorization_code_form(authorization_code, code_verfier) + .as_slice(), + ) + .header("Host", "login.dhl.de") + .header("Accept", "application/json") + .header("Accept-Encoding", "gzip") + .header("User-Agent", constants::token::user_agent()) + .build() + .unwrap(); + + { + let len = req.body().unwrap().as_bytes().unwrap().len(); + let headermap = req.headers_mut(); + headermap.append("Content-Length", len.into()); + } + + let res = self.client.execute(req).await; + + println!("auth_code: {:?}", res); + + match res { + Ok(res) => { + let res = res.text().await.unwrap(); + println!("auth_code reply: {:?}", res); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => res.into(), + Err(err) => Err(LibraryError::from(err)), + } + } + Err(err) => Err(LibraryError::from(err)), + } + } +} diff --git a/libpaket/src/login/openid_response.rs b/libpaket/src/login/openid_response.rs new file mode 100644 index 0000000..ee93aef --- /dev/null +++ b/libpaket/src/login/openid_response.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; +use serde_newtype::newtype; + +use super::DHLIdToken; + +#[derive(Deserialize, Debug)] +pub struct TokenResponse { + access_token: String, + expires_in: u64, + pub id_token: DHLIdToken, + pub refresh_token: RefreshToken, + scope: String, + token_type: String, +} + +// TODO: sanity checks +newtype! { + #[derive(Debug, Clone)] + pub RefreshToken: String[|_k| true, + "", + ]; +} +impl secrecy::SerializableSecret for RefreshToken {} diff --git a/libpaket/src/login/openid_token.rs b/libpaket/src/login/openid_token.rs new file mode 100644 index 0000000..cca3aa3 --- /dev/null +++ b/libpaket/src/login/openid_token.rs @@ -0,0 +1,121 @@ +use std::marker::PhantomData; + +use base64::{engine::general_purpose, Engine as _}; +use serde::{ + de::{DeserializeOwned, Visitor}, + Deserialize, Serialize, +}; + +#[derive(Deserialize, Debug, Clone)] +pub struct Claims { + // only on first time login + pub nonce: Option, + auth_time: u64, + pub iat: u64, + pub exp: u64, + pub aud: Vec, + pub sub: String, + pub iss: String, + + #[serde(flatten)] + pub custom: CustomClaims, +} + +#[derive(Debug, Clone)] +pub struct OpenIDToken { + token: String, + + pub claims: Claims, +} + +impl secrecy::SerializableSecret for OpenIDToken {} + +impl OpenIDToken { + pub fn has_nonce(&self) -> bool { + self.claims.nonce.is_some() + } + + pub fn verify_nonce(&self, nonce: String) -> bool { + if let Some(claims_nonce) = &self.claims.nonce { + return claims_nonce.as_str() == nonce.as_str(); + } + return false; + } + + pub fn to_string(&self) -> String { + self.token.clone() + } + + pub fn as_str(&self) -> &str { + self.token.as_str() + } + + // requires valid system time + pub fn is_expired(&self) -> bool { + let duration: Result = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH); + + let Ok(duration) = duration else { + return true; + }; + + // we artificaly shorten the expiary period, as we don't want it to happen during an api call + duration.as_secs() > self.claims.exp - 30 + } + + pub fn expire_time(&self) -> u64 { + self.claims.exp - 30 + } +} + +impl Serialize for OpenIDToken { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.token.as_str()) + } +} + +impl<'de, T: DeserializeOwned> Deserialize<'de> for OpenIDToken { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(OpenIDTokenVisitor { + phantom_data: PhantomData, + }) + } +} + +struct OpenIDTokenVisitor { + phantom_data: PhantomData, +} +impl<'de, T> Visitor<'de> for OpenIDTokenVisitor +where + T: DeserializeOwned, +{ + type Value = OpenIDToken; + + fn expecting(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result { + todo!() + } + + fn visit_str<'a, E>(self, s: &str) -> Result + where + E: serde::de::Error, + { + + let splits = s.split_terminator(".").collect::>(); + let claims = serde_json::from_slice::>( + general_purpose::URL_SAFE_NO_PAD + .decode(splits[1]) + .unwrap() + .as_slice(), + ).unwrap(); + + Ok(OpenIDToken { + token: s.to_string(), + claims: claims, + }) + } +} diff --git a/libpaket/src/login/utils.rs b/libpaket/src/login/utils.rs new file mode 100644 index 0000000..fdb83d7 --- /dev/null +++ b/libpaket/src/login/utils.rs @@ -0,0 +1,41 @@ +use base64::{engine::general_purpose, Engine as _}; +use sha2::{Digest, Sha256}; + +pub fn create_nonce() -> String { + let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"; + + random_string::generate(22, charset) +} + +#[derive(PartialEq, Eq, Clone)] +pub struct CodeVerfier { + client_secret: String, +} + +impl CodeVerfier { + pub fn new() -> Self { + let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"; + + CodeVerfier { + client_secret: random_string::generate(86, charset), + } + } + + fn sha256(&self) -> Vec { + let hash = { + let mut hasher = Sha256::new(); + hasher.update(self.client_secret.as_bytes()); + hasher.finalize() + }; + + hash.to_vec() + } + + pub fn code_challenge(&self) -> String { + general_purpose::URL_SAFE_NO_PAD.encode(self.sha256()) + } + + pub fn code_verfier(&self) -> String { + self.client_secret.clone() + } +} \ No newline at end of file diff --git a/libpaket/src/stammdaten.rs b/libpaket/src/stammdaten.rs new file mode 100644 index 0000000..aaf0239 --- /dev/null +++ b/libpaket/src/stammdaten.rs @@ -0,0 +1,152 @@ +use reqwest::{header::HeaderMap, Request, RequestBuilder}; + +use crate::www::authorized_credentials; +use crate::constants::{app_version, linux_android_version}; +use crate::common::APIResult; +use crate::{login::DHLIdToken, LibraryResult}; + +pub struct StammdatenClient { + pub(crate) client: reqwest::Client, +} + +// FIXME: Parse more status codes and return LibraryErrors +impl StammdatenClient { + pub fn new() -> Self { + StammdatenClient { + client: reqwest::ClientBuilder::new() + .default_headers(headers()) + .user_agent(user_agent()) + .build() + .unwrap(), + } + } + + pub(crate) fn base_request(&self, request_builder: RequestBuilder, dhli: &DHLIdToken) -> Request { + request_builder + .basic_auth( + authorized_credentials().0, + Some(authorized_credentials().1), + ) + .headers(headers()) + .header("cookie", format!("dhli={}", dhli.as_str())) + .build() + .unwrap() + } + + pub async fn customer_data(&self, dhli: &DHLIdToken) -> LibraryResult { + let req = self.base_request(self.client.get(endpoint_customer_data()), dhli); + + let res = self.client.execute(req).await; + + if let Err(err) = res { + return Err(err.into()); + }; + + let res = res.unwrap().text().await.unwrap(); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => res.into(), + Err(err) => Err(err.into()), + } + } + + pub async fn customer_data_full(&self, dhli: &DHLIdToken) -> LibraryResult { + let req = self.base_request(self.client.get(endpoint_customer_master_data()), dhli); + + let res = self.client.execute(req).await; + + if let Err(err) = res { + return Err(err.into()); + }; + + let res = res.unwrap().text().await.unwrap(); + let res = serde_json::from_str::>(&res); + + match res { + Ok(res) => res.into(), + Err(err) => Err(err.into()), + } + } + + +} + +fn user_agent() -> String { + format!( + "okhttp/4.11.0 Post & DHL/{} ({})", + app_version(), + linux_android_version() + ) +} + +fn headers() -> HeaderMap { + let aaa = vec![ + ("x-api-key", "zAuoC3%7*qbRVmiXdNGyYz9iJ7N@Ph3Cw4zV"), + /* authorization: Basic base dhl.de http credentials */ + /* cookie: dhli= */ + ]; + + let mut map = HeaderMap::new(); + for bbb in aaa { + map.append(bbb.0, bbb.1.parse().unwrap()); + } + + map +} + +// needs authorized +fn endpoint_customer_data() -> &'static str { + "https://www.dhl.de/int-stammdaten/public/customerData" +} + +fn endpoint_customer_master_data() -> &'static str { + "https://www.dhl.de/int-stammdaten/public/customerMasterData" +} + +#[derive(serde::Deserialize, Debug)] +pub struct CustomerData { + //dataConfirmationRequired + #[serde(rename = "displayName")] + pub display_name: String, + #[serde(rename = "emailAddress")] + pub email_address: String, + #[serde(rename = "postNumber")] + pub post_number: String, + pub services: Vec, +} + +#[derive(serde::Deserialize, Debug)] +pub struct CustomerAddress { + pub city: String, + pub country: Option, + #[serde(rename = "houseNumber")] + pub house_number: String, + #[serde(rename = "postalCode")] + pub postal_code: String, + pub street: String, +} + +#[derive(serde::Deserialize, Debug)] +pub enum CustomerDataService { + #[serde(rename = "PACKSTATION")] + Packstation, + #[serde(rename = "PAKETANKUENDIGUNG")] + Paketankuendigung, + #[serde(rename = "POSTFILIALE_DIREKT")] + PostfilialeDirekt, + #[serde(rename = "DIGIBEN")] + Digiben, + #[serde(rename = "GERAET_AKTIVIERT")] + GeraetAktiviert, + #[serde(rename = "BRIEFANKUENDIGUNG")] + Briefankuendigung, +} + +#[derive(serde::Deserialize, Debug)] +pub struct CustomerDataFull { + #[serde(flatten)] + pub common: CustomerData, + + pub address: CustomerAddress, +} diff --git a/libpaket/src/utils.rs b/libpaket/src/utils.rs new file mode 100644 index 0000000..8e31deb --- /dev/null +++ b/libpaket/src/utils.rs @@ -0,0 +1,75 @@ +use crate::login::DHLIdToken; + +pub(crate) fn as_url(input: &str) -> url::Url { + url::Url::parse(input).unwrap() +} + +pub(crate) struct CookieHeaderValueBuilder { + list: Vec<(String, String)>, +} + +impl CookieHeaderValueBuilder { + pub fn new() -> Self { + CookieHeaderValueBuilder { list: Vec::new() } + } + + pub fn add_dhli(mut self, dhli: &DHLIdToken) -> Self { + self.list.push(("dhli".to_string(), dhli.to_string())); + + self + } + + pub fn add_dhlcs(mut self, dhli: &DHLIdToken) -> Self { + self.list.push(("dhlcs".to_string(), dhli.get_dhlcs().to_string())); + + self + } + + pub fn build_string(self) -> String { + let name_value = self + .list + .iter() + .map(|(name, value)| name.clone().to_owned() + "=" + value); + name_value.collect::>().join("; ") + } + + pub fn build_headermap(self) -> reqwest::header::HeaderMap { + let mut map = reqwest::header::HeaderMap::new(); + + self.list.iter().for_each(|(name, value)| { + map.append( + "cookie", + reqwest::header::HeaderValue::from_str(format!("{}={}", name, value).as_str()) + .unwrap(), + ); + }); + + map + } +} + +// TODO: use crate `tracing` +macro_rules! mini_assert_api_eq { + ($a:expr, $b:expr) => { + if $a != $b { + return Err(crate::LibraryError::APIChange); + } + } +} + +macro_rules! mini_assert_api { + ($a: expr) => { + if !$a { + return Err(crate::LibraryError::APIChange); + } + } +} + + +macro_rules! mini_assert_inval { + ($a: expr) => { + if !$a { + return Err(crate::LibraryError::InvalidArgument(format!("MiniAssert failed: $a"))); + } + } +} \ No newline at end of file diff --git a/libpaket/src/www.rs b/libpaket/src/www.rs new file mode 100644 index 0000000..7e6bd07 --- /dev/null +++ b/libpaket/src/www.rs @@ -0,0 +1,50 @@ +use reqwest::header::HeaderMap; + +pub struct WebClient { + pub(crate) web_client: reqwest::Client, +} + +impl WebClient { + pub fn new() -> Self { + WebClient { + web_client: reqwest::ClientBuilder::new() + .default_headers(web_headers()) + .user_agent(crate::constants::webview_user_agent()) + .build() + .unwrap(), + } + } +} + +pub fn web_headers() -> HeaderMap { + let aaa = vec![ + ("accept", "application/json"), + ("pragma", "no-cache"), + ("cache-control", "no-cache,no-store,must-revalidate"), + ("accept-language", "de"), + ("expires", "0"), + ("x-requested-with", "de.dhl.paket"), + ("sec-fetch-site", "same-origin"), + ("sec-fetch-mode", "cors"), + ( + "referer", + "https://www.dhl.de/int-static/pdapp/spa/prod/ver5-SPA-VERFOLGEN.html", + ), + ]; + + let mut map = HeaderMap::new(); + for bbb in aaa { + map.append(bbb.0, bbb.1.parse().unwrap()); + } + + map +} + +pub(crate) fn authorized_credentials() -> (&'static str, &'static str) { + ("erkennen", "8XRUfutM8PTvUz3A") +} + +// "/int-push/", "X-APIKey", "5{@8*nB=?\\.@t,XwWK>Y[=yY^*Y8&myzDE7_" +// /int-stammdaten/", null, "zAuoC3%7*qbRVmiXdNGyYz9iJ7N@Ph3Cw4zV" +// "/int-verfolgen/data/packstation/v2/", null, "a0d5b9049ba8918871e6e20bd5c49974", +// "/", null, "a0d5b9049ba8918871e6e20bd5c49974", diff --git a/paket/Cargo.toml b/paket/Cargo.toml new file mode 100644 index 0000000..91d7c93 --- /dev/null +++ b/paket/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "paket" +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +relm4 = { version = "0.9" , features = [ "libadwaita", "macros" ], path = "/home/jane400/sources/alpine/Relm4/relm4" } +relm4-components = { version = "0.9", path = "/home/jane400/sources/alpine/Relm4/relm4-components" } +relm4-macros = { version = "0.9", path = "/home/jane400/sources/alpine/Relm4/relm4-macros" } +tracker = "0.2" +adw = {package = "libadwaita", version = "0.7", features = [ "v1_5" ]} +webkit = { package = "webkit6", version = "0.4" } +reqwest = "0.12" +libpaket = { path = "../libpaket" } +glycin = { version = "2.0.0-beta", features = ["gdk4"] } +oo7 = { version = "0.3" } diff --git a/paket/src/advices.rs b/paket/src/advices.rs new file mode 100644 index 0000000..98f91c1 --- /dev/null +++ b/paket/src/advices.rs @@ -0,0 +1,106 @@ +use libpaket::advices::UatToken; +use libpaket::LibraryError; +use relm4::gtk; +use gtk::gdk; +use adw::{gio, glib}; + +use relm4::prelude::*; +use adw::prelude::*; +use glib::prelude::*; +use gio::prelude::*; + +#[derive(Debug)] +pub struct AppAdviceMetadata { + pub date: String, + pub advice: libpaket::advices::Advice, +} + +#[tracker::track] +pub struct AppAdvice { + #[do_not_track] + metadata: AppAdviceMetadata, + texture: Option, +} + +#[derive(Debug)] +pub enum AppAdviceCmds { + GotTexture(gdk::Texture), + Error(LibraryError), +} + +#[relm4::factory(pub)] +impl FactoryComponent for AppAdvice { + type Init = (AppAdviceMetadata, UatToken); + type Input = (); + type Output = (); + type CommandOutput = AppAdviceCmds; + type ParentWidget = adw::Carousel; + + view! { + #[root] + gtk::Overlay { + add_overlay = >k::Spinner { + start: (), + set_align: gtk::Align::Center, + + #[track(self.changed_texture())] + set_visible: self.texture.is_none(), + }, + + add_overlay = >k::Label { + set_halign: gtk::Align::Center, + set_valign: gtk::Align::Center, + + set_label: self.metadata.date.as_str(), + }, + + #[wrap(Some)] + set_child = >k::Picture { + #[track(self.changed_texture())] + set_paintable: self.texture.as_ref() + } + } + } + + fn init_model(value: Self::Init, _index: &DynamicIndex, sender: FactorySender) -> Self { + let _self = Self { + metadata: value.0, + texture: None, + tracker: 0, + }; + + let advice = _self.metadata.advice.clone(); + let uat = value.1; + + sender.oneshot_command(async move { + let res = libpaket::advices::AdviceClient::new().fetch_advice_image(&advice, &uat).await; + + let res = match res { + Ok(res) => res, + Err(err) => return AppAdviceCmds::Error(err), + }; + + let file = { + let (file, io_stream) = gio::File::new_tmp(None::<&std::path::Path>).unwrap(); + let output_stream = io_stream.output_stream(); + output_stream.write(res.as_slice(), None::<&gio::Cancellable>).unwrap(); + file + }; + + let image = glycin::Loader::new(file).load().await.expect("Image decoding failed"); + let frame = image.next_frame().await.expect("Image frame decoding failed"); + + AppAdviceCmds::GotTexture(frame.texture()) + }); + + _self + } + + fn update_cmd(&mut self, message: Self::CommandOutput, sender: FactorySender) { + match message { + AppAdviceCmds::GotTexture(texture) => self.set_texture(Some(texture)), + AppAdviceCmds::Error(err) => todo!() + }; + } + +} \ No newline at end of file diff --git a/paket/src/constants.rs b/paket/src/constants.rs new file mode 100644 index 0000000..c5b8963 --- /dev/null +++ b/paket/src/constants.rs @@ -0,0 +1 @@ +pub const APP_ID: &str = "de.j4ne.Paket"; \ No newline at end of file diff --git a/paket/src/login.rs b/paket/src/login.rs new file mode 100644 index 0000000..b491532 --- /dev/null +++ b/paket/src/login.rs @@ -0,0 +1,384 @@ +use std::{ + cell::RefCell, + collections::HashMap, + sync::{Arc, OnceLock}, + time::Duration, +}; + +use adw::prelude::*; +use gtk::prelude::*; +use libpaket::{ + login::{create_nonce, CodeVerfier, DHLIdToken, RefreshToken, TokenResponse}, + LibraryError, LibraryResult, OpenIdClient, +}; +use relm4::{ + adw, gtk, + prelude::*, + tokio::{sync::Mutex, time::sleep}, + AsyncComponentSender, SharedState, +}; +use webkit::{prelude::WebViewExt, URIRequest, WebContext, WebView}; + +static KEYRING: OnceLock = OnceLock::new(); + +#[derive(Debug)] +pub enum LoginInput { + NeedsLogin, + NeedsRefresh, + ReceivedAuthCode(String), + BreakWorld, +} + +#[derive(Debug, PartialEq)] +pub enum LoginState { + InFlow, + Offline, +} + +pub struct LoginFlowModel { + code_verifier: CodeVerfier, + nonce: String, +} + +pub type LoginSharedState = Arc>>>>; + +pub async fn get_id_token(value: &LoginSharedState) -> Option { + let mutex_guard = value.lock().await; + let shared_state_guard = mutex_guard.read(); + shared_state_guard.as_ref().cloned() +} + +#[tracker::track] +pub struct Login { + #[do_not_track] + shared_id_token: LoginSharedState, + #[do_not_track] + refresh_token: Option, + #[do_not_track] + flow_model: RefCell>, + + state: LoginState, +} + +#[derive(Debug)] +pub enum LoginOutput { + RequiresLogin, + RequiresLoading, + NetworkFail, + Error(libpaket::LibraryError), + KeyringError(oo7::Error), +} + +#[derive(Debug)] +pub enum LoginCommand { + Token(LibraryResult), + NeedsRefresh, +} + +const KEYRING_ATTRIBUTES: [(&str, &str); 2] = + [("app", crate::constants::APP_ID), ("type", "refresh_token")]; + +#[relm4::component(async, pub)] +impl AsyncComponent for Login { + type Init = LoginSharedState; + type Input = LoginInput; + type Output = LoginOutput; + type CommandOutput = LoginCommand; + + view! { + #[root] + adw::Bin { + #[wrap(Some)] + #[name = "webview"] + set_child = &WebView::builder().web_context(&webcontext).build() { + #[track(model.changed(Self::state()))] + set_visible: model.state == LoginState::InFlow, + + + #[track(model.changed_state() && model.state == LoginState::InFlow)] + load_request?: &model.construct_request_uri(), + } + } + } + + async fn init( + init: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + // TODO: Make keyring creation failure less fatal :( + let mut model: Login = Login { + shared_id_token: init, + flow_model: RefCell::new(None), + refresh_token: None, + state: LoginState::Offline, + tracker: 0, + }; + + { + let result = oo7::Keyring::new().await; + match result { + Ok(keyring) => { + KEYRING.set(keyring).unwrap(); + { + let keyring = KEYRING.get().unwrap(); + if let Err(err) = keyring.unlock().await { + sender + .output(LoginOutput::KeyringError(err)) + .expect("sender not worky"); + } else { + match keyring + .search_items(&HashMap::from(KEYRING_ATTRIBUTES)) + .await { + Ok(res) => { + if res.len() > 0 { + let item = &res[0]; + let refresh_token = item.secret().await.unwrap(); + let refresh_token = std::str::from_utf8(refresh_token.as_slice()).unwrap(); + model.refresh_token = Some(RefreshToken::new(refresh_token.to_string()).unwrap()); + sender.input(LoginInput::NeedsRefresh); + } else { + sender.input(LoginInput::NeedsLogin); + } + }, + Err(err) => { + sender + .output(LoginOutput::KeyringError(err)) + .expect("sender not worky"); + }, + }; + } + } + } + Err(err) => { + sender + .output(LoginOutput::KeyringError(err)) + .expect("sender not worky"); + } + }; + } + + let webcontext = WebContext::builder().build(); + { + let sender = sender.clone(); + webcontext.register_uri_scheme("dhllogin", move |req| { + let uri = req.uri().unwrap(); + let uri = reqwest::Url::parse(uri.as_str()).unwrap(); + for (name, value) in uri.query_pairs() { + if name == "code" { + sender.input(LoginInput::ReceivedAuthCode(value.to_string())); + return; + } + } + sender.input(LoginInput::BreakWorld); + }); + } + + let widgets = view_output!(); + + let settings = WebViewExt::settings(&widgets.webview).unwrap(); + settings.set_enable_developer_extras(true); + settings.set_user_agent(Some( + libpaket::login::constants::webbrowser_authorize::user_agent().as_str(), + )); + + AsyncComponentParts { model, widgets } + } + + async fn update( + &mut self, + message: Self::Input, + sender: AsyncComponentSender, + _: &Self::Root, + ) { + self.reset(); + + match message { + LoginInput::NeedsRefresh => { + let refresh_token = self.refresh_token.as_ref().unwrap().clone(); + sender.oneshot_command(async { use_refresh_token(refresh_token).await }) + } + LoginInput::ReceivedAuthCode(auth_code) => { + self.set_state(LoginState::Offline); + sender.output(LoginOutput::RequiresLoading).unwrap(); + let model = self.flow_model.borrow(); + let code_verifier = model.as_ref().unwrap().code_verifier.clone(); + sender + .oneshot_command(async { received_auth_code(auth_code, code_verifier).await }); + } + LoginInput::BreakWorld => { + self.set_state(LoginState::Offline); + sender.output(LoginOutput::Error(libpaket::LibraryError::APIChange)).unwrap(); + { + let shared_id_token = self.shared_id_token.lock().await; + let mut shared_id_token = shared_id_token.write(); + *shared_id_token = None; + } + self.flow_model.replace(None); + } + LoginInput::NeedsLogin => { + self.set_state(LoginState::InFlow); + sender.output(LoginOutput::RequiresLogin).unwrap(); + self.flow_model.replace(Some(LoginFlowModel { + code_verifier: CodeVerfier::new(), + nonce: create_nonce(), + })); + } + }; + } + + async fn update_cmd( + &mut self, + message: Self::CommandOutput, + sender: AsyncComponentSender, + _: &Self::Root, + ) { + match message { + LoginCommand::Token(res) => { + self.send_library_response(res, sender).await; + } + LoginCommand::NeedsRefresh => { + sender.input(LoginInput::NeedsRefresh); + } + }; + } +} + +#[derive(PartialEq)] +enum ResponseType { + Retry, + Okay, +} + +impl Login { + fn construct_request_uri(&self) -> Option { + if self.state != LoginState::InFlow { + return None; + }; + let borrow = self.flow_model.borrow(); + let model = borrow.as_ref().unwrap(); + let uri = libpaket::login::constants::webbrowser_authorize::build_endpoint( + &model.nonce, + &model.code_verifier, + ); + Some(URIRequest::new(uri.as_str())) + } + + async fn send_library_response( + &mut self, + res: LibraryResult, + sender: AsyncComponentSender, + ) { + match res { + Ok(res) => { + { + let id_token = res.id_token.clone(); + sender.command(|out, shutdown| { + shutdown + .register(async move { + let unix_target_time = id_token.expire_time(); + + let duration = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap(); + let duration = unix_target_time - duration.as_secs(); + if duration > 0 { + let duration = Duration::from_secs(duration); + sleep(duration).await; + } + out.emit(LoginCommand::NeedsRefresh) + }) + .drop_on_shutdown() + }); + } + let future = async { + self.refresh_token = Some(res.refresh_token); + let keyring = KEYRING.get().unwrap(); + keyring.create_item("Refresh Token", &HashMap::from(KEYRING_ATTRIBUTES), self.refresh_token.as_ref().unwrap().to_string(), true).await.unwrap(); + }; + + if !res.id_token.is_expired() { + let credentials_model = self.shared_id_token.lock().await; + let mut credentials_model = credentials_model.write(); + + *credentials_model = Some(res.id_token); + } + future.await; + } + Err(res) => { + // We disarm the webkit flow/aka breaking the application. We want to reduce invalid requests + match res { + libpaket::LibraryError::APIChange => { + sender.input(LoginInput::BreakWorld); + } + libpaket::LibraryError::InvalidArgument(_) => { + panic!("{}", res); + } + libpaket::LibraryError::NetworkFetch => { + sender.output(LoginOutput::NetworkFail).unwrap(); + } + libpaket::LibraryError::DecodeError(_) => { + sender.output(LoginOutput::Error(res)).unwrap(); + } + libpaket::LibraryError::Unauthorized => { + sender.input(LoginInput::NeedsLogin); + } + } + } + } + } +} + +fn convert_library_error_to_response_type(err: &LibraryError) -> ResponseType { + match err { + LibraryError::NetworkFetch => ResponseType::Retry, + LibraryError::Unauthorized => ResponseType::Okay, + LibraryError::InvalidArgument(_) => ResponseType::Okay, + LibraryError::DecodeError(_) => ResponseType::Okay, + LibraryError::APIChange => ResponseType::Okay, + } +} + +async fn received_auth_code(auth_code: String, code_verifier: CodeVerfier) -> LoginCommand { + let client = OpenIdClient::new(); + let mut err = LibraryError::NetworkFetch; + for _ in 0..6 { + let result: Result = client + .token_authorization(auth_code.clone(), &code_verifier) + .await; + + if let Ok(result) = result { + return LoginCommand::Token(Ok(result)) + } + + err = result.unwrap_err(); + let response_type = convert_library_error_to_response_type(&err); + if response_type == ResponseType::Retry { + continue; + } else { + return LoginCommand::Token(Err(err)); + } + } + LoginCommand::Token(Err(err)) +} + +async fn use_refresh_token(refresh_token: RefreshToken) -> LoginCommand { + let client = OpenIdClient::new(); + let mut err = LibraryError::NetworkFetch; + for _ in 0..6 { + let result: Result = + client.token_refresh(&refresh_token).await; + + err = match result { + Ok(result) => return LoginCommand::Token(Ok(result)), + Err(err) => err, + }; + let response_type = convert_library_error_to_response_type(&err); + if response_type == ResponseType::Retry { + continue; + } else { + return LoginCommand::Token(Err(err)); + } + } + LoginCommand::Token(Err(err)) +} diff --git a/paket/src/main.rs b/paket/src/main.rs new file mode 100644 index 0000000..2041ce8 --- /dev/null +++ b/paket/src/main.rs @@ -0,0 +1,292 @@ +use std::sync::Arc; + +use login::{Login, LoginOutput, LoginSharedState}; +use ready::{Ready, ReadyOutput}; +use relm4::{ + adw, gtk, main_adw_application, prelude::*, tokio::sync::Mutex, + AsyncComponentSender, SharedState, +}; +use gtk::prelude::*; +use adw::{glib, prelude::*}; + +mod advices; +mod constants; +mod login; +mod ready; + +#[derive(Debug, PartialEq)] +enum AppState { + Loading, + RequiresLogIn, + FatalError, + Ready, +} + +#[derive(Debug)] +struct AppError { + short: String, + long: String, +} + +#[derive(Debug)] +enum AppInput { + ErrorOccoured(AppError), + FatalErrorOccoured(AppError), + SwitchToLogin, + SwitchToLoading, + SwitchToReady, + NetworkFail, + Notification(String, u32), +} + +#[tracker::track] +struct App { + state: AppState, + _network_fail: bool, + + #[do_not_track] + login: AsyncController, + #[do_not_track] + ready: Controller, +} + +#[relm4::component(async)] +impl AsyncComponent for App { + type Input = AppInput; + type Output = (); + type Init = (); + type CommandOutput = (); + + view! { + #[root] + main_window = adw::ApplicationWindow::new(&main_adw_application()) { + add_breakpoint = adw::Breakpoint::new( + adw::BreakpointCondition::new_length(adw::BreakpointConditionLengthType::MaxWidth, 550.0, adw::LengthUnit::Sp) + ) { + add_setter: (&ready_headerbar, "show-title", Some(&glib::Value::from(false))), + add_setter: (&ready_switcherbar, "reveal", Some(&glib::Value::from(true))) + }, + + set_default_height: 600, + set_default_width: 800, + set_width_request: 300, + set_height_request: 300, + + #[wrap(Some)] + set_content = &adw::ViewStack { + #[name = "page_prepare"] + add = &adw::ToolbarView { + add_top_bar = &adw::HeaderBar {}, + + #[wrap(Some)] + set_content = prepare_toast_overlay = &adw::ToastOverlay { + #[wrap(Some)] + set_child = &adw::ViewStack { + #[name = "page_loading"] + add = &adw::Bin { + #[wrap(Some)] + set_child = >k::Box { + set_orientation: gtk::Orientation::Vertical, + + gtk::Spinner { + start: (), + } + } + }, + + /* Will be filled in init with a webkit */ + #[local_ref] + add = page_login -> adw::Bin {}, + + #[name = "page_fatal"] + add = &adw::Bin { + #[wrap(Some)] + set_child = fatal_status_page = &adw::StatusPage { + + }, + }, + + #[track(model.changed(App::state()) && model.state != AppState::Ready)] + set_visible_child: { + let page: &adw::Bin = match model.state { + AppState::Loading => page_loading.as_ref(), + AppState::RequiresLogIn => page_login.as_ref(), + AppState::FatalError => page_fatal.as_ref(), + AppState::Ready => panic!(), + }; + page + } + + }, + + }, + }, + + #[name = "page_ready"] + add = &adw::Bin { + #[wrap(Some)] + set_child = &adw::NavigationView { + add = &adw::NavigationPage { + #[wrap(Some)] + set_child = &adw::ToolbarView { + add_top_bar = ready_headerbar = &adw::HeaderBar { + #[wrap(Some)] + set_title_widget = ready_switchertop = &adw::ViewSwitcher{ + set_stack: Some(ready_view_stack), + } + }, + + + #[wrap(Some)] + set_content = ready_toast_overlay = &adw::ToastOverlay { + set_child: Some(ready_view_stack), + }, + + add_bottom_bar = ready_switcherbar = &adw::ViewSwitcherBar { + set_stack: Some(ready_view_stack), + } + } + }, + } + }, + + #[track(model.changed(App::state()) && model.state == AppState::Ready)] + set_visible_child: { + let page: &adw::Bin = page_ready.as_ref(); + page + }, + #[track(model.changed(App::state()) && model.state != AppState::Ready)] + set_visible_child: { + let page: &adw::ToolbarView = page_prepare.as_ref(); + page + }, + } + }, + + } + + async fn init( + _: Self::Init, + root: Self::Root, + sender: AsyncComponentSender, + ) -> AsyncComponentParts { + let login_shared_state = Arc::new(Mutex::new(Arc::new(SharedState::new()))); + + let ready = Ready::builder() + .launch(login_shared_state.clone()) + .forward(sender.input_sender(), convert_ready_response); + + let login = Login::builder() + .launch(login_shared_state.clone()) + .forward(sender.input_sender(), convert_login_response); + + let model = App { + _network_fail: false, + login, + ready, + state: AppState::Loading, + tracker: 0, + }; + + let ready_view_stack = model.ready.widget(); + let page_login = model.login.widget(); + + let widgets = view_output!(); + + AsyncComponentParts { model, widgets } + } + + async fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + message: Self::Input, + sender: AsyncComponentSender, + root: &Self::Root, + ) -> Self::Output { + self.reset(); + match message { + AppInput::ErrorOccoured(error) => { + let dialog: adw::AlertDialog = adw::AlertDialog::builder() + .title(error.short) + .body(error.long) + .build(); + dialog.present(Some(root)); + } + AppInput::SwitchToLoading => { + self.set_state(AppState::Loading); + } + AppInput::SwitchToLogin => { + self.set_state(AppState::RequiresLogIn); + } + AppInput::NetworkFail => { + self.set__network_fail(true); + if self.changed__network_fail() { + sender.input(AppInput::Notification( + "The internet connection is unstable.".to_string(), + 10, + )); + } + } + AppInput::Notification(notification, timeout) => { + let toast_overlay = match self.state { + AppState::Loading => &widgets.prepare_toast_overlay, + AppState::RequiresLogIn => &widgets.prepare_toast_overlay, + AppState::Ready => &widgets.ready_toast_overlay, + AppState::FatalError => &widgets.prepare_toast_overlay, + }; + + toast_overlay.add_toast( + adw::Toast::builder() + .title(notification.as_str()) + .timeout(timeout) + .build(), + ); + } + AppInput::FatalErrorOccoured(error) => { + widgets.fatal_status_page.set_title(&error.short); + widgets.fatal_status_page.set_description(Some(format!("{}\nThis error is fatal, the app can't continue.", &error.long).as_str())); + self.set_state(AppState::FatalError); + } + AppInput::SwitchToReady => { + self.set_state(AppState::Ready); + } + } + self.update_view(widgets, sender); + } +} + +fn convert_login_response(response: LoginOutput) -> AppInput { + match response { + LoginOutput::RequiresLogin => AppInput::SwitchToLogin, + LoginOutput::RequiresLoading => AppInput::SwitchToLoading, + LoginOutput::Error(err) => AppInput::ErrorOccoured(AppError { + short: "An authorization error occured :(".to_string(), + long: err.to_string(), + }), + LoginOutput::NetworkFail => AppInput::NetworkFail, + LoginOutput::KeyringError(err) => AppInput::FatalErrorOccoured(AppError { + short: "Unable to operate on the keyring :(".to_string(), + long: err.to_string(), + }), + } +} + +fn convert_ready_response(response: ReadyOutput) -> AppInput { + match response { + ReadyOutput::FatalError(err) => AppInput::FatalErrorOccoured(AppError { + short: "Unexpted error occured.".to_string(), + long: err.to_string(), + }), + ReadyOutput::NoServicesEnabled => AppInput::FatalErrorOccoured(AppError { + short: "You can't use this app".to_string(), + long: "There is no feature on your account which is supported by this app. You need the offical app and register for one or more of:\n\"Briefasnkündigung\"".to_string(), + }), + ReadyOutput::Error(err) => AppInput::ErrorOccoured(AppError { short: "meow".to_string(), long: err.to_string() }), + ReadyOutput::Ready => AppInput::SwitchToReady, + } +} + +fn main() { + let app = RelmApp::new(constants::APP_ID); + app.run_async::(()); +} diff --git a/paket/src/ready.rs b/paket/src/ready.rs new file mode 100644 index 0000000..071495c --- /dev/null +++ b/paket/src/ready.rs @@ -0,0 +1,286 @@ +// managed the various pages... + +use std::time::Duration; + +use adw::prelude::*; +use libpaket::{ + self, + advices::{AdvicesList, UatToken}, + LibraryError, LibraryResult, +}; +use relm4::{adw, factory::FactoryVecDeque, prelude::*}; + +use crate::advices::AppAdviceMetadata; + +#[derive(Debug, PartialEq)] +pub enum ReadyAdvicesState { + Loading, + HaveNone, + HaveSome, +} + +#[tracker::track] +pub struct Ready { + #[do_not_track] + login: crate::LoginSharedState, + activate: bool, + have_service_advices: bool, + #[do_not_track] + advices_factory: FactoryVecDeque, + advices_state: ReadyAdvicesState, +} + +#[derive(Debug)] +pub enum ReadyOutput { + Ready, + Error(LibraryError), + FatalError(LibraryError), + NoServicesEnabled, +} + +#[derive(Debug)] +pub enum ReadyCmds { + LoggedIn, + LoggedOut, + GotCustomerDataFull(LibraryResult), + RetryAdvices, + GotAdvices((LibraryResult>, Option)), +} + +#[derive(Debug)] +pub enum ReadyInput { + Activate, + Deactivate, + HaveAdvicesService, +} + +#[relm4::component(pub)] +impl Component for Ready { + type Input = ReadyInput; + type Output = ReadyOutput; + type Init = crate::LoginSharedState; + type CommandOutput = ReadyCmds; + + view! { + #[root] + adw::ViewStack { + add = &adw::Bin { + #[wrap(Some)] + set_child = &adw::ViewStack { + #[name = "advices_page_loading"] + add = &adw::StatusPage { + set_title: "Loading mail notifications...", + + }, + #[name = "advices_page_no_available"] + add = &adw::StatusPage { + set_title: "No mail notifications available." + }, + #[name = "advices_page_have_some"] + add = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + + #[local_ref] + advices_carousel -> adw::Carousel { + set_orientation: gtk::Orientation::Vertical, + + }, + + adw::CarouselIndicatorDots { + set_carousel: Some(advices_carousel), + } + }, + + #[track(model.changed_advices_state())] + set_visible_child: { + let page: >k::Widget = match model.advices_state { + ReadyAdvicesState::Loading => advices_page_loading.upcast_ref::(), + ReadyAdvicesState::HaveNone => advices_page_no_available.upcast_ref::(), + ReadyAdvicesState::HaveSome => advices_page_have_some.upcast_ref::(), + }; + page + }, + }, + } -> page_advices: adw::ViewStackPage { + set_title: Some("Mail notification"), + #[track(model.changed_have_service_advices())] + set_visible: model.have_service_advices, + } + } + } + + fn init( + init: Self::Init, + root: Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let advices_factory = FactoryVecDeque::builder() + .launch(adw::Carousel::new()) + .detach(); + + let model = Ready { + have_service_advices: false, + advices_factory, + advices_state: ReadyAdvicesState::Loading, + login: init.clone(), + activate: false, + tracker: 0, + }; + + let advices_carousel = model.advices_factory.widget(); + + let widgets = view_output!(); + { + let login = model.login.clone(); + sender.command(move |out, shutdown| { + shutdown + .register(async move { + let login = { login.clone().as_ref().lock().await.clone() }; + let (sender, receiver) = relm4::channel::(); + login.subscribe(&sender, |model| match model { + Some(_) => ReadyCmds::LoggedIn, + None => ReadyCmds::LoggedOut, + }); + loop { + out.send(receiver.recv().await.unwrap()).unwrap(); + } + }) + .drop_on_shutdown() + }); + } + + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender, _: &Self::Root) { + self.reset(); + + let token = self.login.clone(); + match message { + ReadyInput::Activate => { + self.set_activate(true); + if self.changed_activate() { + sender.oneshot_command(async move { + let token = crate::login::get_id_token(&token).await.unwrap(); + let client = libpaket::StammdatenClient::new(); + ReadyCmds::GotCustomerDataFull(client.customer_data_full(&token).await) + }); + } + } + ReadyInput::Deactivate => { + self.set_activate(false); + } + ReadyInput::HaveAdvicesService => { + sender.oneshot_command(async move { + // fetching advices + let dhli_token = crate::login::get_id_token(&token).await.unwrap(); + let advices = libpaket::WebClient::new().advices(&dhli_token).await; + let advices = match advices { + Ok(res) => res, + Err(err) => return ReadyCmds::GotAdvices((Err(err), None)), + }; + + let mut advices_vec = Vec::new(); + + if let Some(current) = advices.get_current_advice() { + push_advice_from_libpaket_advice(&mut advices_vec, current); + } + + for old_n in advices.get_old_advices() { + push_advice_from_libpaket_advice(&mut advices_vec, old_n); + } + + if advices_vec.len() == 0 { + return ReadyCmds::GotAdvices((Ok(advices_vec), None)); + } + + match libpaket::advices::AdviceClient::new() + .access_token(&advices) + .await + { + Ok(uat_token) => ReadyCmds::GotAdvices((Ok(advices_vec), Some(uat_token))), + Err(err) => ReadyCmds::GotAdvices((Err(err), None)), + } + }) + } + } + } + + fn update_cmd( + &mut self, + message: Self::CommandOutput, + sender: ComponentSender, + _: &Self::Root, + ) { + match message { + ReadyCmds::LoggedIn => sender.input(ReadyInput::Activate), + ReadyCmds::LoggedOut => sender.input(ReadyInput::Deactivate), + ReadyCmds::GotCustomerDataFull(res) => match res { + Ok(res) => { + let mut a_service_was_activated = false; + for service in &res.common.services { + match service { + libpaket::stammdaten::CustomerDataService::Packstation => (), + libpaket::stammdaten::CustomerDataService::Paketankuendigung => (), + libpaket::stammdaten::CustomerDataService::PostfilialeDirekt => (), + libpaket::stammdaten::CustomerDataService::Digiben => (), + libpaket::stammdaten::CustomerDataService::GeraetAktiviert => (), + libpaket::stammdaten::CustomerDataService::Briefankuendigung => { + a_service_was_activated = true; + sender.input(ReadyInput::HaveAdvicesService); + } + } + } + if a_service_was_activated { + sender.output(ReadyOutput::Ready).unwrap() + } else { + sender.output(ReadyOutput::NoServicesEnabled).unwrap(); + } + } + Err(err) => sender.output(ReadyOutput::FatalError(err)).unwrap(), + }, + ReadyCmds::GotAdvices(res) => match res.0 { + Ok(advices_vec) => { + { + let mut guard = self.advices_factory.guard(); + guard.clear(); + } + + if advices_vec.len() == 0 { + self.set_advices_state(ReadyAdvicesState::HaveNone); + } else { + self.set_advices_state(ReadyAdvicesState::HaveSome); + let uat_token = res.1.unwrap(); + let mut guard = self.advices_factory.guard(); + guard.clear(); + for i in advices_vec { + guard.push_back((i, uat_token.clone())); + } + } + } + Err(err) => { + sender.output(ReadyOutput::Error(err)).unwrap(); + sender.oneshot_command(async { + relm4::tokio::time::sleep(Duration::from_secs(30)).await; + ReadyCmds::RetryAdvices + }); + } + }, + ReadyCmds::RetryAdvices => { + sender.input(ReadyInput::HaveAdvicesService); + } + } + } +} + +fn push_advice_from_libpaket_advice( + vec: &mut Vec, + libpaket_advice: &AdvicesList, +) { + for i in &libpaket_advice.list { + vec.push(AppAdviceMetadata { + date: libpaket_advice.date.clone(), + advice: i.clone(), + }); + } +}