diff --git a/Cargo.lock b/Cargo.lock index 6bf58fc..fcf24b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31404e1443b7b7bcaa311c1af456775cc3f668e34573f1503d7ce4844327629e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-core", "futures-sink", @@ -33,16 +33,15 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.13.3" +version = "3.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11004b0e9b44b4eb3d15e0c3132b96fb178c7e50a74758b2f17bb9cc9a7fb4f6" +checksum = "86d62d1a48894ec9450bcde7ef1e3681205771ff6ea9c61cc5ae031145192787" dependencies = [ "actix-codec", - "actix-rt", "actix-service", "actix-utils", "base64 0.22.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "brotli", "bytes", "bytestring", @@ -107,13 +106,12 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.9.1" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d44ae8a6516f4ac7bfc7b61aabcd286104e96b4b24c747ce220832a016056d9" +checksum = "164ab0b702b578c93901482aea90729bcb8b0822b090194372ad2966393778fa" dependencies = [ "actix-rt", "actix-service", - "actix-utils", "futures-core", "futures-util", "mio", @@ -238,17 +236,6 @@ dependencies = [ "ghash", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -352,13 +339,13 @@ checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.1", "password-hash", ] @@ -406,7 +393,7 @@ version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69" dependencies = [ - "ahash 0.8.12", + "ahash", "arrow-buffer", "arrow-data", "arrow-schema", @@ -502,7 +489,7 @@ version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad" dependencies = [ - "ahash 0.8.12", + "ahash", "arrow-array", "arrow-buffer", "arrow-data", @@ -701,7 +688,6 @@ dependencies = [ "base64 0.23.1", "hkdf 0.13.0", "hmac 0.13.0", - "rand_core 0.6.4", "sha2 0.11.0", "thiserror 2.0.20", ] @@ -1027,7 +1013,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1053,9 +1039,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", "zeroize", @@ -1063,9 +1049,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" dependencies = [ "cc", "cmake", @@ -1193,32 +1179,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" dependencies = [ "serde_core", ] -[[package]] -name = "bitvec" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -1257,7 +1231,7 @@ checksum = "dbe8358268799ebb3e4df23cb9d47f4c72bbc4f5247e2fa6a1bf7b6c0baea220" dependencies = [ "async-stream", "base64 0.22.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "bollard-buildkit-proto", "bollard-stubs", "bytes", @@ -1343,7 +1317,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1382,28 +1356,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -1433,9 +1385,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "jobserver", @@ -1650,6 +1602,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpubits" version = "0.1.1" @@ -1736,18 +1694,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1755,27 +1713,27 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -1873,12 +1831,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -1896,15 +1854,15 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -1920,13 +1878,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" dependencies = [ - "darling_core 0.23.0", + "darling_core 0.24.1", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -2060,7 +2018,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2178,11 +2136,17 @@ checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a" dependencies = [ "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -2266,9 +2230,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flate2" @@ -2329,19 +2293,13 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "fuser" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b82b6597d216503555ead6b358f341ef748869bf5c6fbae6a0cb9dd231baecfd" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "libc", "log", "memchr", @@ -2422,7 +2380,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2573,7 +2531,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.14.1", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2592,7 +2550,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.5.0", - "indexmap 2.14.1", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -2616,9 +2574,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] [[package]] name = "hashbrown" @@ -2797,9 +2752,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] @@ -3044,9 +2999,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" +checksum = "edaff2ce006342d4d0e00fae676f7082dada44a203560629ba18ea50a19277bb" [[package]] name = "indexmap" @@ -3061,9 +3016,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -3091,9 +3046,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "itertools" @@ -3242,9 +3197,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -3492,9 +3447,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "log", @@ -3522,13 +3477,40 @@ dependencies = [ "uuid", ] +[[package]] +name = "multiversion" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c" +dependencies = [ + "multiversion-macros", +] + +[[package]] +name = "multiversion-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 3.0.5", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -3541,7 +3523,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -3733,7 +3715,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3927,13 +3909,12 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "getrandom 0.4.3", + "phc", ] [[package]] @@ -3966,6 +3947,17 @@ dependencies = [ "serde", ] +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", + "getrandom 0.4.3", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -4076,9 +4068,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] @@ -4169,7 +4161,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.13.1", + "bitflags 2.13.2", "num-traits", "rand 0.9.5", "rand_chacha 0.9.0", @@ -4212,26 +4204,6 @@ dependencies = [ "prost", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quanta" version = "0.12.6" @@ -4255,9 +4227,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] @@ -4346,12 +4318,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.8" @@ -4452,7 +4418,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -4477,9 +4443,9 @@ dependencies = [ [[package]] name = "redis" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37a4ca5c6ca42aa3e6df2fd32b987a65d32a4c2159a6f3fe0fd1df306a2658f" +checksum = "2acbc41a996f7652b2ddd9dfd98cc4ff602cfd742ae35382f07f608405ab50ed" dependencies = [ "arc-swap", "arcstr", @@ -4512,7 +4478,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -4532,7 +4498,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4570,22 +4536,13 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "encoding_rs", "futures-core", @@ -4646,35 +4603,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rkyv" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "roxmltree" version = "0.21.1" @@ -4706,16 +4634,16 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.42.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.8", - "rkyv", + "rand 0.9.5", "serde", "serde_json", "wasm-bindgen", @@ -4742,7 +4670,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", @@ -4751,9 +4679,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "aws-lc-rs", "log", @@ -5070,12 +4998,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "sec1" version = "0.7.3" @@ -5096,7 +5018,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -5156,7 +5078,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5200,7 +5122,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5217,16 +5139,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.14.1", + "indexmap 2.14.2", "jiff", "schemars 0.9.0", "schemars 1.2.2", @@ -5238,14 +5160,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" dependencies = [ - "darling 0.23.0", + "darling 0.24.1", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -5369,9 +5291,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" dependencies = [ "serde", ] @@ -5447,7 +5369,7 @@ dependencies = [ "futures-util", "hashbrown 0.16.1", "hashlink", - "indexmap 2.14.1", + "indexmap 2.14.2", "log", "memchr", "percent-encoding", @@ -5511,7 +5433,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "bytes", "chrono", @@ -5543,7 +5465,7 @@ checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "chrono", "crc", @@ -5671,17 +5593,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.119" @@ -5695,9 +5606,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -5744,7 +5655,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5765,12 +5676,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" version = "3.27.0" @@ -5852,7 +5757,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5946,9 +5851,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" dependencies = [ "tinyvec_macros", ] @@ -5984,14 +5889,14 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" dependencies = [ "rustls", "tokio", @@ -6033,11 +5938,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.15+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "toml_datetime", "toml_parser", "winnow", @@ -6100,7 +6005,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.14.1", + "indexmap 2.14.2", "pin-project-lite", "slab", "sync_wrapper", @@ -6117,7 +6022,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-util", "http 1.5.0", @@ -6327,7 +6232,7 @@ version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" dependencies = [ - "indexmap 2.14.1", + "indexmap 2.14.2", "serde", "serde_json", "utoipa-gen", @@ -6347,9 +6252,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.26.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -6420,9 +6325,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -6434,9 +6339,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -6444,9 +6349,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6454,22 +6359,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -6489,9 +6394,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -6786,15 +6691,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "xattr" version = "1.6.1" @@ -6857,18 +6753,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" dependencies = [ "proc-macro2", "quote", @@ -6932,7 +6828,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -6958,18 +6854,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.4" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 437b419..a05be55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ actix-web = "4.15.0" actix-governor = "0.10" aes-gcm = "0.11" async-trait = "0.1" -argon2 = "0.5.3" +argon2 = "0.6.0" base64 = { version = "0.23", default-features = false, features = ["std"] } bloomfilter = "3.0.1" bytes = "1" @@ -80,19 +80,18 @@ hmac = "0.13" hkdf = "0.13" http = "1" httpdate = "1" -ipnet = "2.12.1" +ipnet = "2.12.2" lettre = { version = "0.11", default-features = false, features = ["builder", "smtp-transport", "tokio1-rustls-tls"] } md-5 = "0.11" moka = { version = "0.12.16", features = ["future"] } parking_lot = "0.12" percent-encoding = "2.3" proc-macro2 = "1" -quick-xml = { version = "0.41", default-features = false } +quick-xml = { version = "0.42", default-features = false } quote = "1" openidconnect = { version = "4.0.1", default-features = false } rand = { version = "0.10.2", default-features = false, features = ["std", "std_rng", "thread_rng"] } -rand_core_06 = { package = "rand_core", version = "0.6", features = ["getrandom"] } -redis = { version = "1.6.0", features = ["connection-manager", "tokio-comp", "tokio-rustls-comp"] } +redis = { version = "1.7.0", features = ["connection-manager", "tokio-comp", "tokio-rustls-comp"] } reqwest = { version = "0.13", features = ["json"] } sea-orm = { version = "2.0.2", features = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "macros", "runtime-tokio-rustls"] } sea-orm-migration = { version = "2.0.2", default-features = false, features = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls"] } diff --git a/crates/aster_forge_crypto/Cargo.toml b/crates/aster_forge_crypto/Cargo.toml index 99f17bb..5e25757 100644 --- a/crates/aster_forge_crypto/Cargo.toml +++ b/crates/aster_forge_crypto/Cargo.toml @@ -14,7 +14,6 @@ argon2.workspace = true base64.workspace = true hkdf.workspace = true hmac.workspace = true -rand_core_06.workspace = true sha2.workspace = true thiserror.workspace = true diff --git a/crates/aster_forge_crypto/src/hash.rs b/crates/aster_forge_crypto/src/hash.rs index 3be287d..5237637 100644 --- a/crates/aster_forge_crypto/src/hash.rs +++ b/crates/aster_forge_crypto/src/hash.rs @@ -9,12 +9,11 @@ use crate::{CryptoError, Result}; use argon2::{ Algorithm, Argon2, Params, Version, password_hash::{ - Error as PasswordHashError, Output, PasswordHash, PasswordHasher, PasswordVerifier, - SaltString, + Error as PasswordHashError, PasswordHasher, PasswordVerifier, + phc::{Output, PasswordHash}, }, }; use hmac::{Hmac, KeyInit, Mac}; -use rand_core_06::OsRng; use sha2::{Digest, Sha256}; use std::fmt::Write; @@ -311,9 +310,8 @@ pub fn hash_password(password: &str) -> Result { /// Returns an error when the policy's Argon2 parameters are invalid or the password-hash operation /// fails. pub fn hash_password_with_policy(password: &str, policy: &PasswordHashPolicy) -> Result { - let salt = SaltString::generate(&mut OsRng); password_hasher(policy.work_factor)? - .hash_password(password.as_bytes(), &salt) + .hash_password(password.as_bytes()) .map(|hash| hash.to_string()) .map_err(CryptoError::password_hash) } @@ -353,7 +351,7 @@ pub fn verify_password_with_policy( is_valid: true, needs_rehash, }), - Err(PasswordHashError::Password) => Ok(PasswordHashVerification { + Err(PasswordHashError::PasswordInvalid) => Ok(PasswordHashVerification { is_valid: false, needs_rehash: false, }), @@ -370,7 +368,7 @@ fn password_hasher(work_factor: PasswordHashWorkFactor) -> Result, + parsed: &PasswordHash, limits: PasswordHashVerificationLimits, ) -> Result { if parsed.algorithm.as_str() != "argon2id" { @@ -431,18 +429,14 @@ fn validate_stored_password_hash( } fn password_hash_needs_rehash( - parsed: &PasswordHash<'_>, + parsed: &PasswordHash, params: &Params, current: PasswordHashWorkFactor, ) -> Result { let salt = parsed .salt .ok_or_else(|| CryptoError::password_hash("password hash is missing a salt"))?; - let mut salt_bytes = [0_u8; 64]; - let salt_length = salt - .decode_b64(&mut salt_bytes) - .map_err(CryptoError::password_hash)? - .len(); + let salt_length = salt.as_ref().len(); let output_length = params .output_len() .unwrap_or(DEFAULT_PASSWORD_HASH_OUTPUT_LENGTH); @@ -533,7 +527,6 @@ pub fn new_sha256() -> Sha256 { #[cfg(test)] mod tests { use super::*; - use argon2::password_hash::SaltString; use sha2::Digest; fn lightweight_policy() -> PasswordHashPolicy { @@ -553,7 +546,7 @@ mod tests { ) -> String { password_hasher(work_factor) .unwrap() - .hash_password(password.as_bytes(), &SaltString::encode_b64(salt).unwrap()) + .hash_password_with_salt(password.as_bytes(), salt) .unwrap() .to_string() } diff --git a/crates/aster_forge_test/src/state.rs b/crates/aster_forge_test/src/state.rs index 956deb2..b8aaf54 100644 --- a/crates/aster_forge_test/src/state.rs +++ b/crates/aster_forge_test/src/state.rs @@ -193,7 +193,7 @@ impl SharedContainerState { /// Removes resources owned by exited processes, including processes from this nextest run. /// - /// PostgreSQL uses this rolling policy because retaining every isolated database until the + /// `PostgreSQL` uses this rolling policy because retaining every isolated database until the /// next run can exhaust ephemeral CI disks. Live processes and suite-scoped resources remain /// registered, so concurrent tests and reusable templates are not disturbed. pub(crate) fn prune_stale_during_current_execution(&mut self) -> Vec { diff --git a/crates/aster_forge_webdav/src/actix.rs b/crates/aster_forge_webdav/src/actix.rs index 98ac63e..32a3d54 100644 --- a/crates/aster_forge_webdav/src/actix.rs +++ b/crates/aster_forge_webdav/src/actix.rs @@ -10,8 +10,9 @@ use crate::protocol::DavProtocolError; use crate::{ DavBodyError, DavBodyPolicy, DavCapabilityContext, DavCapabilityProvider, DavCapabilitySnapshot, DavCapabilityTarget, DavConditionalPlan, DavConditionalResource, - DavFileSystem, DavIfEvaluationError, DavLockSystem, DavMethod, DavMutationCredentials, DavPath, - DavRequestHead, DavRequestOrigin, DavRequestTarget, DavResponse, DavResponseBody, IfHeader, + DavFileSystem, DavIfEvaluationError, DavLockEnforcementError, DavLockSystem, DavMethod, + DavMutationCredentials, DavParentCollectionError, DavPath, DavRequestHead, DavRequestOrigin, + DavRequestTarget, DavResponse, DavResponseBody, IfHeader, }; /// Request body prepared according to the selected `WebDAV` method contract. @@ -80,32 +81,29 @@ pub fn request_target<'a>( DavRequestHead::parse_target(&uri, mount_path, &origin) } -/// Resolves the product declaration and maps the validated capability snapshot to Actix. +/// Resolves and validates the product capability declaration. /// /// # Errors /// -/// Returns an error response when provider lookup or capability validation fails. +/// Returns a typed failure when provider lookup or capability validation fails. pub async fn capability_snapshot( provider: &Provider, target: &DavCapabilityTarget, context: &DavCapabilityContext, -) -> Result { - crate::plan_capabilities_with_provider(provider, target, context) - .await - .map_err(|error| into_response(crate::capability_evaluation_error_response(&error))) +) -> Result { + crate::plan_capabilities_with_provider(provider, target, context).await } /// Applies the resource-aware dispatch gate to an Actix request method. /// /// # Errors /// -/// Returns a 405 response when the snapshot does not dispatch the request method. +/// Returns [`crate::DavMethodGateError`] when the snapshot does not dispatch the request method. pub fn gate_request_method( request: &HttpRequest, snapshot: &DavCapabilitySnapshot, -) -> Result { +) -> Result { crate::gate_method(DavMethod::from_name(request.method().as_str()), snapshot) - .map_err(|_| into_response(crate::method_not_allowed_response(snapshot))) } /// Converts a transport-neutral response into an Actix response. @@ -151,20 +149,20 @@ pub fn protocol_error_response(error: DavProtocolError) -> HttpResponse { into_response(crate::protocol_error_response(&error)) } -/// Copies Actix headers into the transport-neutral map and maps malformed input to a response. +/// Copies Actix headers into the transport-neutral map. /// /// # Errors /// -/// Returns an error response when an Actix header cannot be represented by `http` 1.x. -pub fn converted_headers(source: &actix_header::HeaderMap) -> Result { - convert_header_map(source).map_err(protocol_error_response) +/// Returns [`DavProtocolError`] when an Actix header cannot be represented by `http` 1.x. +pub fn converted_headers(source: &actix_header::HeaderMap) -> Result { + convert_header_map(source) } /// Resolves and enforces a parsed `WebDAV` `If` header through the canonical backend ports. /// /// # Errors /// -/// Returns an error response when DAV `If` evaluation or backend access fails. +/// Returns [`DavIfEvaluationError`] when DAV `If` evaluation or backend access fails. pub async fn enforce_if_header_with_backends( if_header: Option<&IfHeader>, filesystem: &dyn DavFileSystem, @@ -173,8 +171,8 @@ pub async fn enforce_if_header_with_backends( prefix: &str, request_scheme: &str, request_host: &str, -) -> Result<(), HttpResponse> { - match crate::enforce_if_header_with_backends( +) -> Result<(), DavIfEvaluationError> { + crate::enforce_if_header_with_backends( if_header, filesystem, lock_system, @@ -184,20 +182,13 @@ pub async fn enforce_if_header_with_backends( request_host, ) .await - { - Ok(()) => Ok(()), - Err(DavIfEvaluationError::Protocol(error)) => Err(protocol_error_response(error)), - Err(DavIfEvaluationError::Backend(error)) => { - Err(into_response(crate::backend_error_response(&error))) - } - } } -/// Enforces resource lock submission and maps the protocol response to Actix. +/// Enforces resource lock submission and returns a compact typed failure. /// /// # Errors /// -/// Returns an error response when a conflicting lock exists or lock lookup fails. +/// Returns [`DavLockEnforcementError`] when a conflicting lock exists or lock lookup fails. pub async fn enforce_unlocked( lock_system: &dyn DavLockSystem, path: &DavPath, @@ -206,7 +197,7 @@ pub async fn enforce_unlocked( if_header: Option<&IfHeader>, request_scheme: &str, request_host: &str, -) -> Result { +) -> Result { crate::enforce_unlocked( lock_system, path, @@ -217,14 +208,13 @@ pub async fn enforce_unlocked( request_host, ) .await - .map_err(into_response) } -/// Enforces lock submission for the canonical parent and maps the response to Actix. +/// Enforces lock submission for the canonical parent and returns a compact typed failure. /// /// # Errors /// -/// Returns an error response when the parent is locked or lock lookup fails. +/// Returns [`DavLockEnforcementError`] when the parent is locked or lock lookup fails. pub async fn enforce_parent_unlocked( lock_system: &dyn DavLockSystem, path: &DavPath, @@ -232,7 +222,7 @@ pub async fn enforce_parent_unlocked( if_header: Option<&IfHeader>, request_scheme: &str, request_host: &str, -) -> Result { +) -> Result { crate::enforce_parent_unlocked( lock_system, path, @@ -242,22 +232,71 @@ pub async fn enforce_parent_unlocked( request_host, ) .await - .map_err(into_response) } /// Converts Actix headers and runs the method-aware conditional request planner. /// /// # Errors /// -/// Returns an error response when header conversion or conditional planning fails. +/// Returns a compact typed failure when header conversion or conditional planning fails. pub fn plan_http_conditionals( headers: &actix_header::HeaderMap, method: DavMethod, resource: DavConditionalResource<'_>, -) -> Result { +) -> Result { let headers = converted_headers(headers)?; crate::plan_http_conditionals(method, &headers, resource) - .map_err(|error| into_response(crate::conditional_plan_error_response(&error))) +} + +/// Maps capability evaluation failures to Actix at the handler boundary. +#[must_use] +pub fn capability_error_response(error: &crate::DavCapabilityEvaluationError) -> HttpResponse { + into_response(crate::capability_evaluation_error_response(error)) +} + +/// Maps a rejected method gate to the canonical 405 Actix response. +#[must_use] +pub fn method_gate_error_response(snapshot: &DavCapabilitySnapshot) -> HttpResponse { + into_response(crate::method_not_allowed_response(snapshot)) +} + +/// Maps conditional planning failures to Actix at the handler boundary. +#[must_use] +pub fn conditional_plan_error_response(error: &crate::DavConditionalPlanError) -> HttpResponse { + into_response(crate::conditional_plan_error_response(error)) +} + +/// Maps lock-enforcement failures to Actix at the handler boundary. +#[must_use] +pub fn lock_enforcement_error_response( + error: DavLockEnforcementError, + prefix: &str, +) -> HttpResponse { + match error { + DavLockEnforcementError::Backend(error) => { + into_response(crate::backend_error_response(&error)) + } + DavLockEnforcementError::Conflict { path } => into_response( + crate::lock_conflict_response(prefix, &path) + .unwrap_or_else(|_| DavResponse::empty(http::StatusCode::INTERNAL_SERVER_ERROR)), + ), + } +} + +/// Maps parent-collection enforcement failures to Actix at the handler boundary. +#[must_use] +pub fn parent_collection_error_response(error: DavParentCollectionError) -> HttpResponse { + match error { + DavParentCollectionError::MethodNotAllowed => into_response( + crate::mutation_plan_error_response(crate::DavMutationPlanError::MethodNotAllowed), + ), + DavParentCollectionError::Conflict => into_response(crate::mutation_plan_error_response( + crate::DavMutationPlanError::Conflict, + )), + DavParentCollectionError::Backend(error) => { + into_response(crate::backend_error_response(&error)) + } + } } /// Copies Actix header types into the transport-neutral `http` 1.x map. diff --git a/crates/aster_forge_webdav/src/lib.rs b/crates/aster_forge_webdav/src/lib.rs index b9ecfc9..2443343 100644 --- a/crates/aster_forge_webdav/src/lib.rs +++ b/crates/aster_forge_webdav/src/lib.rs @@ -105,10 +105,11 @@ pub use extension::{ DavResourceStateSet, extension_body_kind, extension_methods, }; pub use lock::{ - DavLockPlan, DavLockPlanError, enforce_parent_unlocked, enforce_unlocked, - lock_acquire_success_response, lock_conflict_response, lock_discovery_element, - lock_limit_response, lock_refresh_success_response, lock_xml_error_response, plan_lock_request, - unlock_success_response, unlock_token_mismatch_response, unsubmitted_lock_conflicts, + DavLockEnforcementError, DavLockPlan, DavLockPlanError, enforce_parent_unlocked, + enforce_unlocked, lock_acquire_success_response, lock_conflict_response, + lock_discovery_element, lock_limit_response, lock_refresh_success_response, + lock_xml_error_response, plan_lock_request, unlock_success_response, + unlock_token_mismatch_response, unsubmitted_lock_conflicts, }; pub use multistatus::{ DavMultiStatusError, DavMultiStatusErrorKind, DavMultiStatusLimits, DavMultiStatusProgress, @@ -154,12 +155,12 @@ pub use request::{ }; pub use resource::{ DavCopyMoveMethod, DavCopyMovePlan, DavMutationFailure, DavMutationPlanError, - DavMutationResponseError, collection_created_response, delete_success_response, - enforce_parent_collection, is_descendant_path, mutation_multistatus_response, - mutation_multistatus_response_with_limits, mutation_plan_error_response, - mutation_success_response, plan_copy_move_request, replace_relative_prefix, - resource_identity_path, same_resource_path, validate_collection_create_target, - validate_delete_target, + DavMutationResponseError, DavParentCollectionError, collection_created_response, + delete_success_response, enforce_parent_collection, is_descendant_path, + mutation_multistatus_response, mutation_multistatus_response_with_limits, + mutation_plan_error_response, mutation_success_response, plan_copy_move_request, + replace_relative_prefix, resource_identity_path, same_resource_path, + validate_collection_create_target, validate_delete_target, }; pub use response::{ DavBodyError, DavDownloadBody, DavDownloadPlan, DavDownloadPlanError, DavMultiRangeLimits, diff --git a/crates/aster_forge_webdav/src/lock.rs b/crates/aster_forge_webdav/src/lock.rs index aada7e4..edc652e 100644 --- a/crates/aster_forge_webdav/src/lock.rs +++ b/crates/aster_forge_webdav/src/lock.rs @@ -39,11 +39,22 @@ pub enum DavLockPlanError { Xml(#[from] DavXmlError), } +/// Failure while enforcing lock-token submission for a mutation. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum DavLockEnforcementError { + /// The lock backend could not be queried. + #[error(transparent)] + Backend(#[from] DavBackendError), + /// A conflicting lock did not have a matching submitted token. + #[error("a conflicting lock token was not submitted for `{path:?}`")] + Conflict { path: DavPath }, +} + /// Rejects an operation when a conflicting lock token was not submitted for its lock root. /// /// # Errors /// -/// Returns a protocol response when lock lookup fails or a conflicting lock is not submitted. +/// Returns a typed failure when lock lookup fails or a conflicting lock is not submitted. pub async fn enforce_unlocked( lock_system: &dyn DavLockSystem, path: &DavPath, @@ -52,7 +63,7 @@ pub async fn enforce_unlocked( if_header: Option<&IfHeader>, request_scheme: &str, request_host: &str, -) -> Result { +) -> Result { let evaluation = evaluate_lock_conflicts( lock_system, path, @@ -63,10 +74,9 @@ pub async fn enforce_unlocked( request_host, ) .await - .map_err(|error| crate::backend_error_response(&error))?; + .map_err(DavLockEnforcementError::from)?; if let Some(lock) = evaluation.unsubmitted.into_iter().next() { - return Err(lock_conflict_response(prefix, &lock.path) - .unwrap_or_else(|_| DavResponse::empty(StatusCode::INTERNAL_SERVER_ERROR))); + return Err(DavLockEnforcementError::Conflict { path: *lock.path }); } Ok(evaluation.credentials) } @@ -154,7 +164,7 @@ pub async fn unsubmitted_lock_conflicts( /// /// # Errors /// -/// Returns a protocol response when the parent lock lookup or submission check fails. +/// Returns a typed failure when the parent lock lookup or submission check fails. pub async fn enforce_parent_unlocked( lock_system: &dyn DavLockSystem, path: &DavPath, @@ -162,7 +172,7 @@ pub async fn enforce_parent_unlocked( if_header: Option<&IfHeader>, request_scheme: &str, request_host: &str, -) -> Result { +) -> Result { let Some(parent_path) = path.parent() else { return Ok(DavMutationCredentials::default()); }; diff --git a/crates/aster_forge_webdav/src/resource.rs b/crates/aster_forge_webdav/src/resource.rs index e8ce3dd..874d30f 100644 --- a/crates/aster_forge_webdav/src/resource.rs +++ b/crates/aster_forge_webdav/src/resource.rs @@ -5,9 +5,9 @@ use http::{HeaderValue, StatusCode}; use crate::response::no_store_empty_response; use crate::{ - DavErrorCondition, DavFileSystem, DavMultiStatusError, DavMultiStatusItem, + DavBackendError, DavErrorCondition, DavFileSystem, DavMultiStatusError, DavMultiStatusItem, DavMultiStatusLimits, DavPath, DavResourceKind, DavResponse, Depth, FsError, - backend_error_response, dav_multistatus_bytes, href_for_dav_path, + dav_multistatus_bytes, href_for_dav_path, }; /// COPY or MOVE operation selected by the request method. @@ -39,6 +39,20 @@ pub enum DavMutationPlanError { PreconditionFailed, } +/// Failure while checking the canonical parent collection of a mutation target. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum DavParentCollectionError { + /// The target is the mount root and therefore has no mutable parent. + #[error("resource mutation is not supported for this target")] + MethodNotAllowed, + /// The canonical parent is missing or is not a collection. + #[error("resource mutation conflicts with the current hierarchy")] + Conflict, + /// The filesystem backend could not resolve the parent. + #[error(transparent)] + Backend(#[from] DavBackendError), +} + /// Rejects collection creation for the DAV root resource. /// /// # Errors @@ -59,25 +73,21 @@ pub fn validate_collection_create_target(path: &str) -> Result<(), DavMutationPl /// /// # Errors /// -/// Returns a protocol response when parent metadata lookup fails or the parent is unsuitable. +/// Returns a typed failure when parent metadata lookup fails or the parent is unsuitable. pub async fn enforce_parent_collection( filesystem: &dyn DavFileSystem, target: &DavPath, -) -> Result<(), DavResponse> { +) -> Result<(), DavParentCollectionError> { let Some(parent) = target.parent() else { - return Err(mutation_plan_error_response( - DavMutationPlanError::MethodNotAllowed, - )); + return Err(DavParentCollectionError::MethodNotAllowed); }; if parent == DavPath::root() { return Ok(()); } match filesystem.metadata(&parent).await { Ok(metadata) if metadata.is_dir() => Ok(()), - Ok(_) | Err(FsError::NotFound) => { - Err(mutation_plan_error_response(DavMutationPlanError::Conflict)) - } - Err(error) => Err(backend_error_response(&error.into())), + Ok(_) | Err(FsError::NotFound) => Err(DavParentCollectionError::Conflict), + Err(error) => Err(DavParentCollectionError::Backend(error.into())), } } diff --git a/crates/aster_forge_webdav/tests/actix.rs b/crates/aster_forge_webdav/tests/actix.rs index ff80649..4169ffe 100644 --- a/crates/aster_forge_webdav/tests/actix.rs +++ b/crates/aster_forge_webdav/tests/actix.rs @@ -286,7 +286,7 @@ fn header_and_precondition_adapter_preserves_success_and_error_contracts() { let malformed_if_match = actix_web::test::TestRequest::default() .insert_header(("If-Match", "bare-etag")) .to_http_request(); - let response = aster_forge_webdav::actix::plan_http_conditionals( + let error = aster_forge_webdav::actix::plan_http_conditionals( malformed_if_match.headers(), DavMethod::Put, DavConditionalResource { @@ -295,7 +295,8 @@ fn header_and_precondition_adapter_preserves_success_and_error_contracts() { last_modified: None, }, ) - .expect_err("malformed If-Match should map to an Actix response"); + .expect_err("malformed If-Match should return a typed error"); + let response = aster_forge_webdav::actix::conditional_plan_error_response(&error); assert_eq!(response.status(), StatusCode::BAD_REQUEST); let converted = aster_forge_webdav::actix::converted_headers(malformed_if_match.headers()) @@ -384,8 +385,13 @@ fn actix_dispatch_gate_uses_snapshot_allow_for_known_and_unknown_methods() { .uri("/webdav/file.txt") .to_http_request(), ] { - let response = aster_forge_webdav::actix::gate_request_method(&request, &snapshot) + let error = aster_forge_webdav::actix::gate_request_method(&request, &snapshot) .expect_err("method should be rejected"); + let response = aster_forge_webdav::actix::method_gate_error_response(&snapshot); + assert_eq!( + error, + aster_forge_webdav::DavMethodGateError::MethodNotAllowed + ); assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!( response.headers().get("Allow").expect("Allow header"), @@ -436,7 +442,8 @@ async fn actix_capability_adapter_distinguishes_invalid_declarations_and_backend &context, ) .await - .expect_err("invalid declaration should map to a response"); + .expect_err("invalid declaration should return a typed error"); + let invalid = aster_forge_webdav::actix::capability_error_response(&invalid); assert_eq!(invalid.status(), StatusCode::INTERNAL_SERVER_ERROR); let forbidden = aster_forge_webdav::actix::capability_snapshot( @@ -445,7 +452,8 @@ async fn actix_capability_adapter_distinguishes_invalid_declarations_and_backend &context, ) .await - .expect_err("provider failure should map to a response"); + .expect_err("provider failure should return a typed error"); + let forbidden = aster_forge_webdav::actix::capability_error_response(&forbidden); assert_eq!(forbidden.status(), StatusCode::FORBIDDEN); } diff --git a/crates/aster_forge_webdav/tests/guard.rs b/crates/aster_forge_webdav/tests/guard.rs index 965b09c..b6b8bbf 100644 --- a/crates/aster_forge_webdav/tests/guard.rs +++ b/crates/aster_forge_webdav/tests/guard.rs @@ -5,12 +5,12 @@ use std::time::{Duration, SystemTime}; use aster_forge_webdav::{ DavBackendError, DavBackendErrorKind, DavConditionalEvaluationError, DavConditionalOutcome, DavConditionalResource, DavFileSystem, DavIfEvaluationError, DavLock, DavLockAcquireRequest, - DavLockAcquireResult, DavLockError, DavLockPreflightError, DavLockSystem, DavMetaData, - DavMethod, DavMutationCredentials, DavPath, DavResponseBody, FsError, FsFuture, LsFuture, - enforce_if_header_with_backends, enforce_parent_collection, enforce_parent_unlocked, - enforce_unlocked, parse_if_header, plan_conditionals_with_backends, unsubmitted_lock_conflicts, + DavLockAcquireResult, DavLockEnforcementError, DavLockError, DavLockPreflightError, + DavLockSystem, DavMetaData, DavMethod, DavMutationCredentials, DavParentCollectionError, + DavPath, FsError, FsFuture, LsFuture, enforce_if_header_with_backends, + enforce_parent_collection, enforce_parent_unlocked, enforce_unlocked, parse_if_header, + plan_conditionals_with_backends, unsubmitted_lock_conflicts, }; -use http::StatusCode; use http::header::{HeaderMap, HeaderValue}; #[derive(Clone)] @@ -400,8 +400,10 @@ fn lock_guard_requires_a_token_scoped_to_the_conflicting_lock_root() { ) .await .expect_err("a token tagged for another resource must not unlock this target"); - assert_eq!(response.status, StatusCode::LOCKED); - assert!(matches!(response.body, DavResponseBody::Bytes(_))); + assert!(matches!( + response, + DavLockEnforcementError::Conflict { path } if path.as_str() == "/locked/" + )); let credentials = match enforce_unlocked( &lock_system, @@ -415,10 +417,9 @@ fn lock_guard_requires_a_token_scoped_to_the_conflicting_lock_root() { .await { Ok(credentials) => credentials, - Err(response) => panic!( - "a token tagged for the lock root should unlock the target, got {}", - response.status - ), + Err(error) => { + panic!("a token tagged for the lock root should unlock the target, got {error:?}") + } }; assert_eq!( credentials.submitted_lock_tokens, @@ -463,10 +464,9 @@ fn lock_guard_does_not_forward_negated_or_unrelated_tokens_as_credentials() { .await { Ok(credentials) => credentials, - Err(response) => panic!( - "the positively submitted root token should satisfy the lock, got {}", - response.status - ), + Err(error) => { + panic!("the positively submitted root token should satisfy the lock, got {error:?}") + } }; assert_eq!( @@ -496,10 +496,9 @@ fn parent_lock_guard_checks_the_canonical_parent_and_skips_mount_root() { .await { Ok(credentials) => credentials, - Err(response) => panic!( - "the parent-scoped token should allow the mutation, got {}", - response.status - ), + Err(error) => { + panic!("the parent-scoped token should allow the mutation, got {error:?}") + } }; assert_eq!( credentials.submitted_lock_tokens, @@ -533,10 +532,9 @@ fn parent_lock_guard_checks_the_canonical_parent_and_skips_mount_root() { .await { Ok(credentials) => credentials, - Err(response) => panic!( - "canonical parent must not be percent-decoded again, got {}", - response.status - ), + Err(error) => { + panic!("canonical parent must not be percent-decoded again, got {error:?}") + } }; assert_eq!( credentials.submitted_lock_tokens, @@ -563,10 +561,7 @@ fn parent_lock_guard_checks_the_canonical_parent_and_skips_mount_root() { .await; match root_result { Ok(credentials) => assert!(credentials.submitted_lock_tokens.is_empty()), - Err(response) => panic!( - "the mount root has no parent to check, got {}", - response.status - ), + Err(error) => panic!("the mount root has no parent to check, got {error:?}"), } assert!( root_locks @@ -684,11 +679,11 @@ fn lock_guard_fails_closed_when_conflict_lookup_fails() { ) .await .expect_err("backend lock lookup failure must reject the mutation"); - assert_eq!(response.status, StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!( - response.headers.get(http::header::CACHE_CONTROL), - Some(&HeaderValue::from_static("no-store")) - ); + assert!(matches!( + response, + DavLockEnforcementError::Backend(error) + if error.kind == DavBackendErrorKind::Internal + )); }); } @@ -705,7 +700,10 @@ fn parent_collection_guard_covers_root_collection_missing_and_backend_boundaries enforce_parent_collection(&filesystem, &DavPath::new("/").expect("mount root")) .await .expect_err("the mount root cannot be created below itself"); - assert_eq!(root_error.status, StatusCode::METHOD_NOT_ALLOWED); + assert!(matches!( + root_error, + DavParentCollectionError::MethodNotAllowed + )); assert!( enforce_parent_collection(&filesystem, &DavPath::new("/new.txt").expect("root child")) @@ -743,7 +741,7 @@ fn parent_collection_guard_covers_root_collection_missing_and_backend_boundaries ) .await .expect_err("a file or missing resource cannot be a collection parent"); - assert_eq!(response.status, StatusCode::CONFLICT); + assert!(matches!(response, DavParentCollectionError::Conflict)); } let response = enforce_parent_collection( @@ -752,6 +750,10 @@ fn parent_collection_guard_covers_root_collection_missing_and_backend_boundaries ) .await .expect_err("backend errors must retain their protocol classification"); - assert_eq!(response.status, StatusCode::FORBIDDEN); + assert!(matches!( + response, + DavParentCollectionError::Backend(error) + if error.kind == aster_forge_webdav::DavBackendErrorKind::Forbidden + )); }); } diff --git a/crates/aster_forge_xml/benches/support/mod.rs b/crates/aster_forge_xml/benches/support/mod.rs index bdb17ee..e8b5505 100644 --- a/crates/aster_forge_xml/benches/support/mod.rs +++ b/crates/aster_forge_xml/benches/support/mod.rs @@ -102,10 +102,7 @@ pub(crate) fn walk_quick_xml_ns_buffered(input: &[u8]) -> usize { )) .wrapping_add( attribute - .decoded_and_normalized_value( - XmlVersion::Explicit1_0, - reader.decoder(), - ) + .normalized_value(XmlVersion::Explicit1_0) .expect("benchmark value is valid") .len(), ); @@ -119,9 +116,9 @@ pub(crate) fn walk_quick_xml_ns_buffered(input: &[u8]) -> usize { )); } Event::Text(text) => { - let decoded = text.decode().expect("benchmark text is valid"); + let decoded = text.as_ref(); checksum = checksum.wrapping_add( - unescape(&decoded) + unescape(decoded) .expect("benchmark text entities are valid") .len(), ); @@ -135,8 +132,7 @@ pub(crate) fn walk_quick_xml_ns_buffered(input: &[u8]) -> usize { ); } Event::CData(text) => { - checksum = - checksum.wrapping_add(text.decode().expect("benchmark CDATA is valid").len()); + checksum = checksum.wrapping_add(text.as_ref().len()); } Event::Comment(_) | Event::PI(_) | Event::Decl(_) | Event::DocType(_) => {} Event::Eof => break, @@ -160,7 +156,7 @@ pub(crate) fn walk_forge_stream(input: &[u8]) -> usize { loop { match reader.read_event().expect("benchmark fixture is valid XML") { XmlStreamEvent::Start(start) | XmlStreamEvent::Empty(start) => { - let name = start.name().expect("benchmark name is valid"); + let name = start.name(); checksum = checksum .wrapping_add(name.qualified().len()) .wrapping_add(name.namespace().map_or(0, str::len)); @@ -174,7 +170,7 @@ pub(crate) fn walk_forge_stream(input: &[u8]) -> usize { } } XmlStreamEvent::End(end) => { - let name = end.name().expect("benchmark name is valid"); + let name = end.name(); checksum = checksum .wrapping_add(name.qualified().len()) .wrapping_add(name.namespace().map_or(0, str::len)); diff --git a/crates/aster_forge_xml/src/document.rs b/crates/aster_forge_xml/src/document.rs index 45f6f5f..060de38 100644 --- a/crates/aster_forge_xml/src/document.rs +++ b/crates/aster_forge_xml/src/document.rs @@ -13,8 +13,8 @@ use quick_xml::escape::unescape; use quick_xml::events::{BytesStart, Event}; use crate::syntax::{ - XML_NAMESPACE_URI, map_quick_xml_error_at, split_qualified_name, utf8, - validate_namespace_binding, validate_qualified_name, + XML_NAMESPACE_URI, map_quick_xml_error_at, split_qualified_name, validate_namespace_binding, + validate_qualified_name, }; use crate::{Error, ParseOptions, XmlSafetyError, XmlSafetyPolicy}; @@ -796,19 +796,20 @@ impl<'a> DocumentBuilder<'a> { } Event::End(_) => self.end_element(event_end)?, Event::Text(text) => { - let raw = utf8(text.as_ref())?; + let raw = text.as_ref(); let value = unescape(raw).map_err(|error| Error::InvalidXml(error.to_string()))?; self.text_node(value, false)?; } - Event::CData(text) => self.text_node(Cow::Borrowed(utf8(text.as_ref())?), true)?, + Event::CData(text) => self.text_node(Cow::Borrowed(text.as_ref()), true)?, Event::Comment(comment) => { - let value = self.source_value(utf8(comment.as_ref())?)?; + let value = self.source_value(comment.as_ref())?; self.push_content(NodeKind::Comment(value))?; } Event::PI(pi) => { - let target = self.source_value(utf8(pi.target())?)?; - let content = utf8(pi.content())? + let target = self.source_value(pi.target())?; + let content = pi + .content() .trim_start_matches(|character: char| character.is_ascii_whitespace()); let content = (!content.is_empty()) .then(|| self.source_value(content)) @@ -823,7 +824,7 @@ impl<'a> DocumentBuilder<'a> { Cow::Owned(character.to_string()) } else { Cow::Owned( - match utf8(reference.as_ref())? { + match reference.as_ref() { "amp" => "&", "lt" => "<", "gt" => ">", @@ -905,7 +906,7 @@ impl<'a> DocumentBuilder<'a> { )] fn build_element( &mut self, - reader: &Reader<&[u8]>, + _reader: &Reader<&[u8]>, start: &BytesStart<'a>, source_start: u64, source_end: u64, @@ -914,7 +915,7 @@ impl<'a> DocumentBuilder<'a> { return Err(XmlSafetyError::Malformed.into()); } let start_name = start.name(); - let qualified_name = utf8(start_name.as_ref())?; + let qualified_name = start_name.as_ref(); let (prefix, _) = validate_qualified_name(qualified_name)?; let parent_scope = self.open.last().and_then(|id| { let NodeKind::Element(element) = &self.nodes[id.index()].kind else { @@ -932,12 +933,12 @@ impl<'a> DocumentBuilder<'a> { return Err(XmlSafetyError::TooManyAttributes.into()); } let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?; - let name = utf8(attribute.key.as_ref())?; + let name = attribute.key.as_ref(); validate_qualified_name(name)?; if name == "xmlns" || name.starts_with("xmlns:") { let namespace_prefix = name.strip_prefix("xmlns:").unwrap_or(""); let uri = attribute - .decoded_and_normalized_value(XmlVersion::Explicit1_0, reader.decoder()) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string()))?; validate_namespace_binding(namespace_prefix, &uri)?; let prefix_value = if namespace_prefix.is_empty() { @@ -979,7 +980,7 @@ impl<'a> DocumentBuilder<'a> { let attribute_start = arena_len(self.attributes.len(), "attributes")?; for attribute in start.attributes() { let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?; - let name = utf8(attribute.key.as_ref())?; + let name = attribute.key.as_ref(); if name == "xmlns" || name.starts_with("xmlns:") { continue; } @@ -994,7 +995,7 @@ impl<'a> DocumentBuilder<'a> { return Err(XmlSafetyError::Malformed.into()); } let value = attribute - .decoded_and_normalized_value(XmlVersion::Explicit1_0, reader.decoder()) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string()))?; let qualified_name = self.source_value(name)?; let value = self.cow_value(value)?; diff --git a/crates/aster_forge_xml/src/parser.rs b/crates/aster_forge_xml/src/parser.rs index 38cb992..f67c9a9 100644 --- a/crates/aster_forge_xml/src/parser.rs +++ b/crates/aster_forge_xml/src/parser.rs @@ -8,8 +8,8 @@ use quick_xml::escape::unescape; use quick_xml::events::{BytesStart, Event}; use crate::syntax::{ - XML_NAMESPACE_URI, map_quick_xml_error_at, split_qualified_name, utf8, - validate_namespace_binding, validate_qualified_name, + XML_NAMESPACE_URI, map_quick_xml_error_at, split_qualified_name, validate_namespace_binding, + validate_qualified_name, }; use crate::{DEFAULT_XML_MAX_DEPTH, Error, XmlSafetyError}; @@ -284,7 +284,7 @@ fn scan_xml(bytes: &[u8], options: &ParseOptions) -> Result, Erro } Event::End(end) => { let end_name = end.name(); - let qualified_name = utf8(end_name.as_ref())?; + let qualified_name = end_name.as_ref(); let frame = state.frames.pop().ok_or(XmlSafetyError::Malformed)?; if frame.qualified_name != qualified_name { return Err(XmlSafetyError::Malformed.into()); @@ -295,13 +295,13 @@ fn scan_xml(bytes: &[u8], options: &ParseOptions) -> Result, Erro } } Event::Text(text) => { - let raw = utf8(text.as_ref())?; + let raw = text.as_ref(); let value = unescape(raw).map_err(|error| Error::InvalidXml(error.to_string()))?; state.count_text(value.as_ref(), options.safety)?; } - Event::CData(text) => state.count_text(utf8(text.as_ref())?, options.safety)?, + Event::CData(text) => state.count_text(text.as_ref(), options.safety)?, Event::GeneralRef(reference) => { - let value = decode_reference(reference.as_ref(), &reference)?; + let value = decode_reference(&reference)?; state.count_text(value.as_ref(), options.safety)?; } Event::Decl(_) => { @@ -317,13 +317,7 @@ fn scan_xml(bytes: &[u8], options: &ParseOptions) -> Result, Erro return Err(XmlSafetyError::Malformed.into()); } } - Event::Comment(comment) => { - utf8(comment.as_ref())?; - } - Event::PI(pi) => { - utf8(pi.target())?; - utf8(pi.content())?; - } + Event::Comment(_) | Event::PI(_) => {} Event::Eof => { if !state.frames.is_empty() || !state.root_complete { return Err(XmlSafetyError::Malformed.into()); @@ -335,7 +329,7 @@ fn scan_xml(bytes: &[u8], options: &ParseOptions) -> Result, Erro } fn scan_element( - reader: &Reader<&[u8]>, + _reader: &Reader<&[u8]>, state: &mut ScanState, start: &BytesStart<'_>, policy: XmlSafetyPolicy, @@ -344,7 +338,7 @@ fn scan_element( return Err(XmlSafetyError::Malformed.into()); } let start_name = start.name(); - let qualified_name = utf8(start_name.as_ref())?; + let qualified_name = start_name.as_ref(); let (prefix, local_name) = validate_qualified_name(qualified_name)?; let binding_start = state.bindings.len(); let mut attribute_count = 0usize; @@ -357,12 +351,12 @@ fn scan_element( return Err(XmlSafetyError::TooManyAttributes.into()); } let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?; - let name = utf8(attribute.key.as_ref())?; + let name = attribute.key.as_ref(); validate_qualified_name(name)?; if name == "xmlns" || name.starts_with("xmlns:") { let namespace_prefix = name.strip_prefix("xmlns:").unwrap_or(""); let uri = attribute - .decoded_and_normalized_value(XmlVersion::Explicit1_0, reader.decoder()) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string()))?; validate_namespace_binding(namespace_prefix, &uri)?; state.bindings.push(NamespaceBinding { @@ -379,7 +373,7 @@ fn scan_element( } for attribute in start.attributes() { let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?; - let name = utf8(attribute.key.as_ref())?; + let name = attribute.key.as_ref(); if name == "xmlns" || name.starts_with("xmlns:") { continue; } @@ -402,7 +396,6 @@ fn scan_element( } fn decode_reference<'a>( - bytes: &'a [u8], reference: &quick_xml::events::BytesRef<'a>, ) -> Result, Error> { if let Some(character) = reference @@ -411,7 +404,7 @@ fn decode_reference<'a>( { return Ok(Cow::Owned(character.to_string())); } - Ok(Cow::Borrowed(match utf8(bytes)? { + Ok(Cow::Borrowed(match reference.as_ref() { "amp" => "&", "lt" => "<", "gt" => ">", diff --git a/crates/aster_forge_xml/src/stream.rs b/crates/aster_forge_xml/src/stream.rs index 36cf70b..d7f3655 100644 --- a/crates/aster_forge_xml/src/stream.rs +++ b/crates/aster_forge_xml/src/stream.rs @@ -5,7 +5,6 @@ use std::io::{BufRead, Take, Write}; use aster_forge_utils::numbers::usize_to_u64; use quick_xml::XmlVersion; -use quick_xml::encoding::Decoder; use quick_xml::escape::unescape; use quick_xml::events::attributes::{Attribute, Attributes as QuickAttributes}; use quick_xml::events::{BytesCData, BytesEnd, BytesPI, BytesStart, BytesText, Event}; @@ -13,7 +12,7 @@ use quick_xml::name::{NamespaceResolver, PrefixDeclaration, ResolveResult}; use quick_xml::reader::NsReader; use quick_xml::writer::Writer; -use crate::syntax::{map_quick_xml_error, utf8}; +use crate::syntax::map_quick_xml_error; use crate::{Error, ValidatedXml, XmlSafetyError, XmlSafetyPolicy}; /// A namespace-resolved XML name borrowed from one streaming event. @@ -51,24 +50,21 @@ pub struct StreamStart<'a> { raw: BytesStart<'a>, namespace: Option<&'a str>, resolver: &'a NamespaceResolver, - decoder: Decoder, cached_attribute_values: &'a [CachedAttributeValue], } impl StreamStart<'_> { /// Resolves the start element's qualified, local, and namespace names. /// - /// # Errors - /// - /// Returns an error when the encoded element name is not valid UTF-8. - pub fn name(&self) -> Result, Error> { - let qualified = utf8(self.raw.name().into_inner())?; - let local = utf8(self.raw.local_name().into_inner())?; - Ok(StreamName { + #[must_use] + pub fn name(&self) -> StreamName<'_> { + let qualified = self.raw.name().into_inner(); + let local = self.raw.local_name().into_inner(); + StreamName { qualified, local, namespace: self.namespace, - }) + } } #[must_use] @@ -76,7 +72,6 @@ impl StreamStart<'_> { StreamAttributes { inner: self.raw.attributes(), resolver: self.resolver, - decoder: self.decoder, cached_values: self.cached_attribute_values, cached_index: 0, index: 0, @@ -124,7 +119,6 @@ impl StreamStart<'_> { pub struct StreamAttributes<'a> { inner: QuickAttributes<'a>, resolver: &'a NamespaceResolver, - decoder: Decoder, cached_values: &'a [CachedAttributeValue], cached_index: usize, index: usize, @@ -152,7 +146,6 @@ impl<'a> Iterator for StreamAttributes<'a> { .map(|raw| StreamAttribute { raw, resolver: self.resolver, - decoder: self.decoder, cached_value, }) .map_err(|error| Error::InvalidXml(error.to_string())) @@ -164,7 +157,6 @@ impl<'a> Iterator for StreamAttributes<'a> { pub struct StreamAttribute<'a> { raw: Attribute<'a>, resolver: &'a NamespaceResolver, - decoder: Decoder, cached_value: Option<&'a str>, } @@ -173,10 +165,10 @@ impl<'a> StreamAttribute<'a> { /// /// # Errors /// - /// Returns an error when the encoded name is invalid or its namespace cannot be resolved. + /// Returns an error when its namespace cannot be resolved. pub fn name(&self) -> Result, Error> { - let qualified = utf8(self.raw.key.into_inner())?; - let local = utf8(self.raw.key.local_name().into_inner())?; + let qualified = self.raw.key.into_inner(); + let local = self.raw.key.local_name().into_inner(); let namespace = resolve_namespace( self.resolver.resolve_attribute(self.raw.key).0, "attribute namespace", @@ -192,13 +184,13 @@ impl<'a> StreamAttribute<'a> { /// /// # Errors /// - /// Returns an error when the attribute value has invalid encoding or entity syntax. + /// Returns an error when the attribute value has invalid entity syntax. pub fn value(&self) -> Result, Error> { if let Some(value) = self.cached_value { return Ok(Cow::Borrowed(value)); } self.raw - .decoded_and_normalized_value(XmlVersion::Explicit1_0, self.decoder) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string())) } @@ -206,13 +198,13 @@ impl<'a> StreamAttribute<'a> { /// /// # Errors /// - /// Returns an error when the attribute value has invalid encoding or entity syntax. + /// Returns an error when the attribute value has invalid entity syntax. pub fn into_value(self) -> Result, Error> { if let Some(value) = self.cached_value { return Ok(Cow::Borrowed(value)); } self.raw - .decoded_and_normalized_value(XmlVersion::Explicit1_0, self.decoder) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string())) } } @@ -227,17 +219,15 @@ pub struct StreamEnd<'a> { impl StreamEnd<'_> { /// Resolves the end element's qualified, local, and namespace names. /// - /// # Errors - /// - /// Returns an error when the encoded element name is not valid UTF-8. - pub fn name(&self) -> Result, Error> { - let qualified = utf8(self.raw.name().into_inner())?; - let local = utf8(self.raw.local_name().into_inner())?; - Ok(StreamName { + #[must_use] + pub fn name(&self) -> StreamName<'_> { + let qualified = self.raw.name().into_inner(); + let local = self.raw.local_name().into_inner(); + StreamName { qualified, local, namespace: self.namespace, - }) + } } } @@ -285,22 +275,20 @@ pub struct StreamProcessingInstruction<'a> { impl StreamProcessingInstruction<'_> { /// Returns the processing-instruction target. /// - /// # Errors - /// - /// Returns an error when the target is not valid UTF-8. - pub fn target(&self) -> Result<&str, Error> { - utf8(self.raw.target()) + #[must_use] + pub fn target(&self) -> &str { + self.raw.target() } /// Returns trimmed processing-instruction content when present. /// - /// # Errors - /// - /// Returns an error when the content is not valid UTF-8. - pub fn content(&self) -> Result, Error> { - let content = utf8(self.raw.content())? + #[must_use] + pub fn content(&self) -> Option<&str> { + let content = self + .raw + .content() .trim_start_matches(|character: char| character.is_ascii_whitespace()); - Ok((!content.is_empty()).then_some(content)) + (!content.is_empty()).then_some(content) } } @@ -365,7 +353,14 @@ impl XmlStreamReader { reader.config_mut().trim_text(false); reader .resolver_mut() - .set_max_declarations_per_element(policy.max_attributes_per_element); + // quick-xml 0.42 bounds namespace bindings currently in scope, while Forge's + // attribute limit applies to one element. Keep the limits independent and derive + // the in-scope bound from the maximum active depth and per-element declarations. + .set_max_namespace_bindings( + policy + .max_depth + .saturating_mul(policy.max_attributes_per_element), + ); Ok(Self { reader, buffer: Vec::new(), @@ -423,7 +418,6 @@ impl XmlStreamReader { raw: start, namespace, resolver: self.reader.resolver(), - decoder: self.reader.decoder(), cached_attribute_values: &self.cached_attribute_values, })) } @@ -439,7 +433,6 @@ impl XmlStreamReader { raw: start, namespace, resolver: self.reader.resolver(), - decoder: self.reader.decoder(), cached_attribute_values: &self.cached_attribute_values, })) } @@ -451,7 +444,6 @@ impl XmlStreamReader { self.reader.resolver().resolve_element(end.name()).0, "element namespace", )?; - utf8(end.name().into_inner())?; self.state.depth -= 1; if self.state.depth == 0 { self.state.root_complete = true; @@ -467,25 +459,17 @@ impl XmlStreamReader { Ok(XmlStreamEvent::Text(StreamText { value })) } Event::CData(cdata) => { - let value = cdata - .decode() - .map_err(|_| XmlSafetyError::InvalidEncoding)?; + let value = Cow::Owned(cdata.as_ref().to_owned()); count_text(&mut self.state, &value)?; Ok(XmlStreamEvent::CData(StreamCData { value })) } Event::Comment(comment) => { - let value = comment - .decode() - .map_err(|_| XmlSafetyError::InvalidEncoding)?; + let value = Cow::Owned(comment.as_ref().to_owned()); Ok(XmlStreamEvent::Comment(StreamComment { value })) } - Event::PI(pi) => { - utf8(pi.target())?; - utf8(pi.content())?; - Ok(XmlStreamEvent::ProcessingInstruction( - StreamProcessingInstruction { raw: pi }, - )) - } + Event::PI(pi) => Ok(XmlStreamEvent::ProcessingInstruction( + StreamProcessingInstruction { raw: pi }, + )), Event::GeneralRef(reference) => { let value = decode_reference(&reference)?; count_text(&mut self.state, &value)?; @@ -594,20 +578,19 @@ impl XmlStreamReader { return false; }; match prefix { - PrefixDeclaration::Default => attribute.key.as_ref() == b"xmlns", + PrefixDeclaration::Default => attribute.key.as_ref() == "xmlns", PrefixDeclaration::Named(prefix) => { - attribute.key.as_ref().strip_prefix(b"xmlns:") == Some(prefix) + attribute.key.as_ref().strip_prefix("xmlns:") == Some(prefix) } } }); if already_declared { continue; } - let namespace = utf8(namespace.into_inner())?; + let namespace = namespace.into_inner(); match prefix { PrefixDeclaration::Default => captured_start.push_attribute(("xmlns", namespace)), PrefixDeclaration::Named(prefix) => { - let prefix = utf8(prefix)?; let name = format!("xmlns:{prefix}"); captured_start.push_attribute((name.as_str(), namespace)); } @@ -718,20 +701,17 @@ fn begin_element<'a, R: BufRead>( reader.resolver().resolve_element(start.name()).0, "element namespace", )?; - utf8(start.name().into_inner())?; - for (index, attribute) in start.attributes().enumerate() { let attribute = attribute.map_err(|error| Error::InvalidXml(error.to_string()))?; if index >= state.policy.max_attributes_per_element { return Err(XmlSafetyError::TooManyAttributes.into()); } - utf8(attribute.key.into_inner())?; resolve_namespace( reader.resolver().resolve_attribute(attribute.key).0, "attribute namespace", )?; let value = attribute - .decoded_and_normalized_value(XmlVersion::Explicit1_0, reader.decoder()) + .normalized_value(XmlVersion::Explicit1_0) .map_err(|error| Error::InvalidXml(error.to_string()))?; if let Cow::Owned(value) = value { cached_attribute_values.push(CachedAttributeValue { index, value }); @@ -766,24 +746,17 @@ fn count_text(state: &mut StreamState, value: &str) -> Result<(), Error> { fn resolve_namespace<'a>(result: ResolveResult<'a>, label: &str) -> Result, Error> { match result { ResolveResult::Unbound => Ok(None), - ResolveResult::Bound(namespace) => utf8(namespace.into_inner()).map(Some), + ResolveResult::Bound(namespace) => Ok(Some(namespace.into_inner())), ResolveResult::Unknown(prefix) => Err(Error::InvalidXml(format!( - "unknown {label} prefix `{}`", - String::from_utf8_lossy(&prefix) + "unknown {label} prefix `{prefix}`" ))), } } fn decode_text<'a>(text: &BytesText<'a>) -> Result, Error> { - match text.decode().map_err(|_| XmlSafetyError::InvalidEncoding)? { - Cow::Borrowed(value) => { - unescape(value).map_err(|error| Error::InvalidXml(error.to_string())) - } - Cow::Owned(value) => unescape(&value) - .map(Cow::into_owned) - .map(Cow::Owned) - .map_err(|error| Error::InvalidXml(error.to_string())), - } + unescape(text.as_ref()) + .map(|value| Cow::Owned(value.into_owned())) + .map_err(|error| Error::InvalidXml(error.to_string())) } fn decode_reference<'a>( @@ -795,7 +768,7 @@ fn decode_reference<'a>( { return Ok(Cow::Owned(character.to_string())); } - Ok(Cow::Borrowed(match utf8(reference.as_ref())? { + Ok(Cow::Borrowed(match reference.as_ref() { "amp" => "&", "lt" => "<", "gt" => ">", diff --git a/crates/aster_forge_xml/src/syntax.rs b/crates/aster_forge_xml/src/syntax.rs index ce3a85b..e8db115 100644 --- a/crates/aster_forge_xml/src/syntax.rs +++ b/crates/aster_forge_xml/src/syntax.rs @@ -5,10 +5,6 @@ use crate::{Error, XmlSafetyError}; pub(crate) const XML_NAMESPACE_URI: &str = "http://www.w3.org/XML/1998/namespace"; pub(crate) const XMLNS_NAMESPACE_URI: &str = "http://www.w3.org/2000/xmlns/"; -pub(crate) fn utf8(bytes: &[u8]) -> Result<&str, Error> { - std::str::from_utf8(bytes).map_err(|_| XmlSafetyError::InvalidEncoding.into()) -} - pub(crate) fn validate_qualified_name(name: &str) -> Result<(Option<&str>, &str), Error> { let (prefix, local) = split_qualified_name(name); if !valid_name(local) diff --git a/crates/aster_forge_xml/src/writer.rs b/crates/aster_forge_xml/src/writer.rs index 64d822b..a636313 100644 --- a/crates/aster_forge_xml/src/writer.rs +++ b/crates/aster_forge_xml/src/writer.rs @@ -437,18 +437,15 @@ impl XmlStreamWriter { } for attribute in start.attributes() { let attribute = attribute.map_err(|error| Error::InvalidData(error.to_string()))?; - let attribute_name = std::str::from_utf8(attribute.key.as_ref()) - .map_err(|_| Error::InvalidData("attribute name is not UTF-8".into()))?; + let attribute_name = attribute.key.as_ref(); self.validate_attribute_namespace(attribute_name)?; } for (index, left) in start.attributes().enumerate() { let left = left.map_err(|error| Error::InvalidData(error.to_string()))?; - let left_name = std::str::from_utf8(left.key.as_ref()) - .map_err(|_| Error::InvalidData("attribute name is not UTF-8".into()))?; + let left_name = left.key.as_ref(); for right in start.attributes().skip(index + 1) { let right = right.map_err(|error| Error::InvalidData(error.to_string()))?; - let right_name = std::str::from_utf8(right.key.as_ref()) - .map_err(|_| Error::InvalidData("attribute name is not UTF-8".into()))?; + let right_name = right.key.as_ref(); if self.attributes_share_expanded_name(left_name, right_name) { return Err(Error::InvalidData(format!( "attributes `{left_name}` and `{right_name}` have the same expanded name" diff --git a/crates/aster_forge_xml/tests/property.rs b/crates/aster_forge_xml/tests/property.rs index 8be8207..9557166 100644 --- a/crates/aster_forge_xml/tests/property.rs +++ b/crates/aster_forge_xml/tests/property.rs @@ -89,8 +89,7 @@ fn assert_forge_model(element: ElementRef<'_, &[u8]>, model: &ElementModel) { assert_eq!(element.text().unwrap_or_default(), model.text); assert_eq!(element.attributes().count(), model.attributes.len()); for (name, value) in &model.attributes { - let normalized = normalize_attribute_value(value); - assert_eq!(element.attribute(name), Some(normalized.as_str())); + assert_eq!(element.attribute(name), Some(value.as_str())); } let actual_children: Vec<_> = element.child_elements().collect(); assert_eq!(actual_children.len(), model.children.len()); @@ -109,8 +108,7 @@ fn assert_roxmltree_model(node: roxmltree::Node<'_, '_>, model: &ElementModel) { assert_eq!(direct_text, model.text); assert_eq!(node.attributes().len(), model.attributes.len()); for (name, value) in &model.attributes { - let normalized = normalize_attribute_value(value); - assert_eq!(node.attribute(name.as_str()), Some(normalized.as_str())); + assert_eq!(node.attribute(name.as_str()), Some(value.as_str())); } let actual_children: Vec<_> = node .children() @@ -122,16 +120,6 @@ fn assert_roxmltree_model(node: roxmltree::Node<'_, '_>, model: &ElementModel) { } } -fn normalize_attribute_value(value: &str) -> String { - value - .chars() - .map(|character| match character { - '\n' | '\r' | '\t' => ' ', - character => character, - }) - .collect() -} - proptest! { #![proptest_config(ProptestConfig::with_cases(256))] diff --git a/crates/aster_forge_xml/tests/stream.rs b/crates/aster_forge_xml/tests/stream.rs index 09f282c..6aa057e 100644 --- a/crates/aster_forge_xml/tests/stream.rs +++ b/crates/aster_forge_xml/tests/stream.rs @@ -16,7 +16,7 @@ fn streams_namespace_resolved_names_attributes_and_text() { let XmlStreamEvent::Start(root) = reader.read_event().expect("root") else { panic!("expected root start"); }; - assert!(root.name().expect("name").matches("root", Some("DAV:"))); + assert!(root.name().matches("root", Some("DAV:"))); assert_eq!( root.attribute_ns("id", Some("urn:attr")) .expect("attribute") @@ -27,7 +27,7 @@ fn streams_namespace_resolved_names_attributes_and_text() { let XmlStreamEvent::Start(name) = reader.read_event().expect("name") else { panic!("expected name start"); }; - assert!(name.name().expect("name").matches("name", Some("DAV:"))); + assert!(name.name().matches("name", Some("DAV:"))); let text = reader.read_text_current().expect("direct text"); assert_eq!(text, "a&b"); @@ -73,7 +73,7 @@ fn skips_unselected_subtrees_without_a_token_stack() { let XmlStreamEvent::Start(keep) = reader.read_event().expect("keep") else { panic!("expected keep start"); }; - assert_eq!(keep.name().expect("name").local(), "keep"); + assert_eq!(keep.name().local(), "keep"); assert_eq!(reader.read_text_current().expect("keep text"), "yes"); } @@ -86,12 +86,7 @@ fn captures_only_selected_subtree_and_injects_in_scope_namespaces() { let XmlStreamEvent::Start(color) = reader.read_event().expect("color") else { panic!("expected color start"); }; - assert!( - color - .name() - .expect("name") - .matches("color", Some("urn:property")) - ); + assert!(color.name().matches("color", Some("urn:property"))); let captured = reader.capture_current(1024).expect("captured subtree"); let root = captured.document().root(); @@ -171,6 +166,29 @@ fn stream_enforces_input_depth_attribute_text_and_event_limits() { } } +#[test] +fn nested_namespace_bindings_do_not_reuse_the_attribute_limit() { + let input = br#""#; + let policy = XmlSafetyPolicy { + max_depth: 4, + max_attributes_per_element: 1, + ..XmlSafetyPolicy::untrusted() + }; + let mut reader = + XmlStreamReader::new(BufReader::new(input.as_slice()), policy).expect("policy"); + let mut events = 0; + loop { + match reader + .read_event() + .expect("nested namespace bindings are valid") + { + XmlStreamEvent::Eof => break, + _ => events += 1, + } + } + assert_eq!(events, 5); +} + #[test] fn stream_rejects_doctype_unknown_prefix_and_multiple_roots() { for input in [ @@ -227,12 +245,7 @@ fn streams_and_selectively_captures_a_hundred_thousand_element_document() { loop { match reader.read_event().expect("stream event") { - XmlStreamEvent::Start(start) - if start - .name() - .expect("name") - .matches("response", Some("DAV:")) => - { + XmlStreamEvent::Start(start) if start.name().matches("response", Some("DAV:")) => { responses += 1; if responses == items / 2 { captured = Some(reader.capture_current(256).expect("capture one response")); diff --git a/crates/aster_forge_xml/tests/xmltree_compat.rs b/crates/aster_forge_xml/tests/xmltree_compat.rs index 3c9aee8..c4d1eca 100644 --- a/crates/aster_forge_xml/tests/xmltree_compat.rs +++ b/crates/aster_forge_xml/tests/xmltree_compat.rs @@ -86,7 +86,7 @@ fn xmltree_snapshot(element: &xmltree::Element) -> ElementSnapshot { } } -fn inside(source: &[u8], slice: &[u8]) -> bool { +fn inside(source: &[u8], slice: &str) -> bool { let source_start = source.as_ptr() as usize; let source_end = source_start + source.len(); let slice_start = slice.as_ptr() as usize; @@ -123,12 +123,9 @@ fn forge_arena_borrows_plain_values_from_the_source() { let document = BorrowedDocument::parse(source.as_slice()).expect("fixture should parse"); let root = document.root(); - assert!(inside(source, root.qualified_name().as_bytes())); - assert!(inside( - source, - root.attribute("plain").expect("attribute").as_bytes() - )); - assert!(inside(source, root.text().expect("text").as_bytes())); + assert!(inside(source, root.qualified_name())); + assert!(inside(source, root.attribute("plain").expect("attribute"))); + assert!(inside(source, &root.text().expect("text"))); assert_eq!(document.allocated_value_count(), 0); } diff --git a/docs/crates/aster_forge_webdav.md b/docs/crates/aster_forge_webdav.md index 1d99b0b..eacc95a 100644 --- a/docs/crates/aster_forge_webdav.md +++ b/docs/crates/aster_forge_webdav.md @@ -356,7 +356,10 @@ Forge 负责: - `DavLockSystem` 的 discovery、batch discovery 和 conflict lookup 都返回 typed backend failure;产品 adapter 不得把查询失败降级成空锁集合,Forge 会让 `If`、mutation guard 和 `lockdiscovery` fail closed。 - 批量 dead-property 读取只向 backend 传递 `DavPath`;产品 adapter 自行解析数据库身份并执行批量查询。 - Actix transport 与 transport-neutral `http` 类型的显式转换。Actix 仍使用 `http 0.2` 而 Forge 公共模型使用 `http 1.x`,URI/header 跨版本转换保持显式边界。 -- Actix adapter 统一完成 header conversion、协议/后端错误响应和 HTTP ETag/`If` guard 映射。 +- Actix adapter 在 handler 边界完成 header conversion、协议/后端错误响应和 HTTP ETag/`If` guard + 映射;`capability_snapshot`、method gate、lock guard、conditional planner 和 parent-collection + guard 本身只返回紧凑的 typed error,不把 `HttpResponse` 放进 `Result` 的错误分支。这样不会为 + 常规控制流错误引入 `Box` 堆分配,也不会扩大 async Future 的错误变体。 - OPTIONS、405、body-policy failure 和 download response 的 product-neutral response shell。 - resource-aware capability declaration/snapshot、RFC class 与扩展 prerequisite 校验、静态 package/property/report descriptor、canonical `Allow`/`DAV`/`DASL`/`Accept-Patch` rendering diff --git a/docs/crates/aster_forge_xml.md b/docs/crates/aster_forge_xml.md index 1bd92fa..fa435d9 100644 --- a/docs/crates/aster_forge_xml.md +++ b/docs/crates/aster_forge_xml.md @@ -79,14 +79,14 @@ let mut reader = XmlStreamReader::new(BufReader::new(body.as_slice()), XmlSafety loop { match reader.read_event()? { - XmlStreamEvent::Start(start) if start.name()?.matches("href", Some("DAV:")) => { + XmlStreamEvent::Start(start) if start.name().matches("href", Some("DAV:")) => { assert_eq!(reader.read_text_current()?, "/a"); } - XmlStreamEvent::Start(start) if start.name()?.matches("unknown", Some("urn:extension")) => { + XmlStreamEvent::Start(start) if start.name().matches("unknown", Some("urn:extension")) => { let retained = reader.capture_current(64 * 1024)?; assert_eq!(retained.document().root().namespace(), Some("urn:extension")); } - XmlStreamEvent::Start(start) if start.name()?.matches("ignored", None) => reader.skip_current()?, + XmlStreamEvent::Start(start) if start.name().matches("ignored", None) => reader.skip_current()?, XmlStreamEvent::Eof => break, _ => {} } @@ -95,6 +95,11 @@ loop { `XmlStreamReader` 复用 event buffer,只保留当前事件、namespace resolver 和与深度成正比的 namespace state,不保留完整 token 列表或 DOM。`capture_current(max_bytes)` 只 materialize 当前 subtree,并自动补齐祖先作用域内、该 subtree 独立解析所需的 namespace declaration;`skip_current()` 继续执行完整安全和 well-formedness 检查,但不会保留被跳过节点。 +事件 name、processing-instruction 和文本内容在 `quick-xml 0.42` 中直接以 UTF-8 字符串暴露,因此 +`StreamStart::name`、`StreamEnd::name`、`StreamProcessingInstruction::target/content` 不再返回 +额外的 UTF-8 解码错误;属性值仍通过 `normalized_value` 执行 XML entity 与属性规范化。writer 会用 +字符引用保留属性中的换行、回车和制表符,使 writer → parser 往返不丢失这些值。 + ## Streaming writer ```rust diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..fc6e6f1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.94" +profile = "minimal" +components = ["clippy", "llvm-tools-preview", "rustfmt"]