From 9793dfb9cd41fae0b78ef518e5d6af4aafe5fb65 Mon Sep 17 00:00:00 2001 From: "przydatek@github.com" Date: Mon, 7 Oct 2024 10:56:21 +0200 Subject: [PATCH 1/4] feat(ic-http-gateway): enable validation of long assets' chunks --- Cargo.lock | 670 +++++++++--------- Cargo.toml | 10 +- .../canister/src/custom_assets/Cargo.toml | 8 +- .../canister/src/custom_assets/src/lib.rs | 185 +++-- packages/ic-http-gateway/Cargo.toml | 3 + .../ic-http-gateway/src/protocol/handler.rs | 97 ++- .../src/response/response_handler.rs | 52 +- .../tests/range_request_stream.rs | 88 ++- 8 files changed, 564 insertions(+), 549 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cee6f8..d418b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -46,9 +46,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arrayvec" @@ -75,13 +75,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -92,9 +92,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backoff" @@ -109,17 +109,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -223,9 +223,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cached" @@ -236,7 +236,7 @@ dependencies = [ "ahash", "cached_proc_macro", "cached_proc_macro_types", - "hashbrown", + "hashbrown 0.14.5", "instant", "once_cell", "thiserror", @@ -249,7 +249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8466736fe5dbcaf8b8ee24f9bbefe43c884dc3e9ff7178da70f55bffca1133c" dependencies = [ "ahash", - "hashbrown", + "hashbrown 0.14.5", "instant", "once_cell", "thiserror", @@ -275,9 +275,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "candid" -version = "0.10.9" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df77a80c72fcd356cf37ff59c812f37ff06dc9a81232b3aff0a308cb5996904" +checksum = "6c30ee7f886f296b6422c0ff017e89dd4f831521dfdcc76f3f71aae1ce817222" dependencies = [ "anyhow", "binread", @@ -305,14 +305,17 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] name = "cc" -version = "1.1.6" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -347,15 +350,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -601,9 +604,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -680,7 +683,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -695,6 +698,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" @@ -737,9 +746,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -749,15 +758,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -773,9 +782,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -806,6 +815,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -890,12 +905,14 @@ dependencies = [ "ic-asset-certification", "ic-cdk", "ic-cdk-macros", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification)", - "ic-http-certification 2.6.0 (git+https://github.com/dfinity/response-verification)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-http-certification", "include_dir", "lazy_static", + "rand_chacha", "serde", "serde_cbor", + "sha2 0.10.8", ] [[package]] @@ -913,9 +930,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -946,9 +963,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", @@ -965,9 +982,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -978,7 +995,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -1027,22 +1043,23 @@ dependencies = [ [[package]] name = "ic-asset-certification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification#f8a7f42ecfe7be437e4f3e3752b57dbf9b3c9aa9" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "globset", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification)", - "ic-http-certification 2.6.0 (git+https://github.com/dfinity/response-verification)", + "http 0.2.12", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-http-certification", + "regex", "thiserror", ] [[package]] name = "ic-cbor" version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b0e48b4166c891e79d624f3a184b4a7c145d307576872d9a46dedb8c73ea8f" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "candid", - "ic-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", "leb128", "nom", "thiserror", @@ -1050,9 +1067,9 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8859bc2b863a77750acf199e1fb7e3fc403e1b475855ba13f59cb4e4036d238" +checksum = "3b1da6a25b045f9da3c9459c0cb2b0700ac368ee16382975a17185a23b9c18ab" dependencies = [ "candid", "ic-cdk-macros", @@ -1078,13 +1095,12 @@ dependencies = [ [[package]] name = "ic-certificate-verification" version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586e09b06a93d930f6a33f5f909bb11d2e4a06be3635dd5da1eb0e6554b7dae4" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "cached 0.47.0", "candid", "ic-cbor", - "ic-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", "lazy_static", "leb128", "miracl_core_bls12381", @@ -1109,7 +1125,7 @@ dependencies = [ [[package]] name = "ic-certification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification#f8a7f42ecfe7be437e4f3e3752b57dbf9b3c9aa9" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "hex", "serde", @@ -1120,28 +1136,15 @@ dependencies = [ [[package]] name = "ic-http-certification" version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0b97e949845039149dc5e7ea6a7c12ee4333bb402e37bc507904643c7b3e41" -dependencies = [ - "candid", - "http 0.2.12", - "ic-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ic-representation-independent-hash 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "thiserror", - "urlencoding", -] - -[[package]] -name = "ic-http-certification" -version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification#f8a7f42ecfe7be437e4f3e3752b57dbf9b3c9aa9" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ + "base64 0.21.7", "candid", "http 0.2.12", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification)", - "ic-representation-independent-hash 2.6.0 (git+https://github.com/dfinity/response-verification)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-representation-independent-hash", "serde", + "serde_cbor", "thiserror", "urlencoding", ] @@ -1158,11 +1161,14 @@ dependencies = [ "http-body", "http-body-util", "ic-agent", - "ic-http-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ic-http-certification", "ic-response-verification", "ic-utils", "pocket-ic", + "rand_chacha", "regex", + "rstest", + "sha2 0.10.8", "thiserror", "tokio", ] @@ -1170,17 +1176,7 @@ dependencies = [ [[package]] name = "ic-representation-independent-hash" version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ae59483e377cd9aad94ec339ed1d2583b0d5929cab989328dac2d853b2f570" -dependencies = [ - "leb128", - "sha2 0.10.8", -] - -[[package]] -name = "ic-representation-independent-hash" -version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification#f8a7f42ecfe7be437e4f3e3752b57dbf9b3c9aa9" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "leb128", "sha2 0.10.8", @@ -1189,8 +1185,7 @@ dependencies = [ [[package]] name = "ic-response-verification" version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef02ef84189d61a7d39889b7e9a3ae212d45c3df293513f7b2568027fd08a8" +source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" dependencies = [ "base64 0.21.7", "candid", @@ -1199,9 +1194,9 @@ dependencies = [ "http 0.2.12", "ic-cbor", "ic-certificate-verification", - "ic-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ic-http-certification 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ic-representation-independent-hash 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-http-certification", + "ic-representation-independent-hash", "leb128", "log", "nom", @@ -1334,12 +1329,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1353,9 +1348,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itoa" @@ -1365,18 +1360,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -1400,9 +1395,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "lock_api" @@ -1449,18 +1444,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", @@ -1531,18 +1526,21 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -1585,9 +1583,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -1609,7 +1607,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1643,26 +1641,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1706,6 +1684,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1714,9 +1698,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty" @@ -1749,25 +1736,26 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ "cc", ] [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 1.1.0", + "rustc-hash", "rustls", + "socket2", "thiserror", "tokio", "tracing", @@ -1782,7 +1770,7 @@ dependencies = [ "bytes", "rand", "ring", - "rustc-hash 2.0.0", + "rustc-hash", "rustls", "slab", "thiserror", @@ -1792,21 +1780,22 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", - "windows-sys 0.52.0", + "tracing", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1849,23 +1838,23 @@ checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1879,13 +1868,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1896,15 +1885,21 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -1945,7 +1940,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", + "windows-registry", ] [[package]] @@ -1973,6 +1968,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.79", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1981,21 +2005,24 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] -name = "rustc-hash" -version = "2.0.0" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -2007,9 +2034,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -2020,25 +2047,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2059,11 +2085,11 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2087,7 +2113,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2125,9 +2151,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -2141,9 +2167,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -2169,13 +2195,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2186,16 +2212,17 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2208,7 +2235,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2267,6 +2294,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2341,15 +2374,15 @@ dependencies = [ [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2374,7 +2407,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2402,9 +2435,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2416,25 +2449,28 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2495,9 +2531,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.1" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2519,7 +2555,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2535,9 +2571,9 @@ dependencies = [ [[package]] name = "tokio-socks" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", @@ -2547,9 +2583,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2558,32 +2594,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2616,7 +2631,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] @@ -2691,30 +2706,30 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "untrusted" @@ -2747,9 +2762,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -2768,34 +2783,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2805,9 +2821,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2815,28 +2831,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -2847,9 +2863,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2857,9 +2873,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -2887,12 +2903,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-targets 0.48.5", + "windows-result", + "windows-targets", ] [[package]] @@ -2901,22 +2938,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2925,46 +2956,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2977,70 +2990,37 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -3052,7 +3032,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 94d3765..c861b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,9 +33,11 @@ http-body-util = "0.1" bytes = "1" base64 = "0.22" lazy_static = "1" +rand_chacha = "0.3" regex = "1" serde = "1" serde_cbor = "0.11" +sha2 = "0.10" tokio = { version = "1", features = ["full"] } hyper = { version = "1", features = ["full"] } hyper-util = "0.1" @@ -47,8 +49,12 @@ ic-utils = "0.37" candid = "0.10" pocket-ic = "=3.0.0" assert_matches = "1.5" +rstest = "0.18" -ic-http-certification = "2" -ic-response-verification = "2" + +ic-response-verification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } +ic-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } +ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } +ic-asset-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } ic-http-gateway = { path = "./packages/ic-http-gateway", version = "0.0.0" } diff --git a/examples/http-gateway/canister/src/custom_assets/Cargo.toml b/examples/http-gateway/canister/src/custom_assets/Cargo.toml index 54f6c7a..34deef2 100644 --- a/examples/http-gateway/canister/src/custom_assets/Cargo.toml +++ b/examples/http-gateway/canister/src/custom_assets/Cargo.toml @@ -12,10 +12,12 @@ ic-cdk.workspace = true ic-cdk-macros.workspace = true serde.workspace = true serde_cbor.workspace = true +sha2.workspace = true lazy_static.workspace = true base64.workspace = true include_dir = { version = "0.7", features = ["glob"] } +rand_chacha.workspace = true -ic-certification = { git = "https://github.com/dfinity/response-verification" } -ic-http-certification = { git = "https://github.com/dfinity/response-verification", features = ["serde"] } -ic-asset-certification = { git = "https://github.com/dfinity/response-verification" } +ic-certification.workspace = true +ic-http-certification.workspace = true +ic-asset-certification.workspace = true diff --git a/examples/http-gateway/canister/src/custom_assets/src/lib.rs b/examples/http-gateway/canister/src/custom_assets/src/lib.rs index 9760688..8c6df50 100644 --- a/examples/http-gateway/canister/src/custom_assets/src/lib.rs +++ b/examples/http-gateway/canister/src/custom_assets/src/lib.rs @@ -1,13 +1,11 @@ -use base64::engine::general_purpose::STANDARD as BASE64; -use base64::Engine; use ic_asset_certification::{Asset, AssetConfig, AssetFallbackConfig, AssetRouter}; use ic_cdk::{ api::{data_certificate, set_certified_data}, *, }; -use ic_certification::HashTree; use ic_http_certification::{HeaderField, HttpRequest, HttpResponse}; -use serde::Serialize; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; use std::cell::RefCell; #[init] @@ -22,34 +20,85 @@ fn post_upgrade() { #[query] fn http_request(req: HttpRequest) -> HttpResponse { - serve_asset(&req) + ic_cdk::println!("*** serving request ***: {:?}", req); + let resp = serve_asset(&req); + ic_cdk::println!( + "\n+++ returning resp: {}, {:?}", + resp.status_code(), + resp.headers() + ); + resp } thread_local! { static ASSET_ROUTER: RefCell> = Default::default(); } -static ASSET_206_BODY: &[u8; 64] = - b"Some asset that returns a 206-response"; +const ASSET_CHUNK_SIZE: usize = 2_000_000; + +const ONE_CHUNK_ASSET_LEN: usize = ASSET_CHUNK_SIZE; +const TWO_CHUNKS_ASSET_LEN: usize = ASSET_CHUNK_SIZE + 1; +const SIX_CHUNKS_ASSET_LEN: usize = 5 * ASSET_CHUNK_SIZE + 12; +const TEN_CHUNKS_ASSET_LEN: usize = 10 * ASSET_CHUNK_SIZE; + +const ONE_CHUNK_ASSET_NAME: &str = "long_asset_one_chunk"; +const TWO_CHUNKS_ASSET_NAME: &str = "long_asset_two_chunks"; +const SIX_CHUNKS_ASSET_NAME: &str = "long_asset_six_chunks"; +const TEN_CHUNKS_ASSET_NAME: &str = "long_asset_ten_chunks"; + +use ic_certification::Hash; +use sha2::{Digest, Sha256}; + +pub fn hash(data: T) -> Hash +where + T: AsRef<[u8]>, +{ + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().into() +} + +fn long_asset_body(asset_name: &str) -> Vec { + let asset_length = match asset_name { + ONE_CHUNK_ASSET_NAME => ONE_CHUNK_ASSET_LEN, + TWO_CHUNKS_ASSET_NAME => TWO_CHUNKS_ASSET_LEN, + SIX_CHUNKS_ASSET_NAME => SIX_CHUNKS_ASSET_LEN, + TEN_CHUNKS_ASSET_NAME => TEN_CHUNKS_ASSET_LEN, + _ => ASSET_CHUNK_SIZE * 3 + 1, + }; + let mut rng = ChaCha20Rng::from_seed(hash(asset_name)); + let mut body = vec![0u8; asset_length]; + rng.fill_bytes(&mut body); + body +} fn certify_all_assets() { - let asset_configs = vec![ - AssetConfig::File { - path: "index.html".to_string(), - content_type: Some("text/html".to_string()), - headers: get_asset_headers(vec![( - "cache-control".to_string(), - "public, no-cache, no-store".to_string(), - )]), - fallback_for: vec![AssetFallbackConfig { - scope: "/".to_string(), - }], - aliased_by: vec!["/".to_string()], - encodings: vec![], - }, - AssetConfig::File { - path: "asset_206".to_string(), - content_type: Some("text/html".to_string()), + let mut assets = vec![Asset::new( + "index.html", + b"Hello, world!", + )]; + let mut asset_configs = vec![AssetConfig::File { + path: "index.html".to_string(), + content_type: Some("text/html".to_string()), + headers: get_asset_headers(vec![( + "cache-control".to_string(), + "public, no-cache, no-store".to_string(), + )]), + fallback_for: vec![AssetFallbackConfig { + scope: "/".to_string(), + }], + aliased_by: vec!["/".to_string()], + encodings: vec![], + }]; + for asset_name in [ + ONE_CHUNK_ASSET_NAME, + TWO_CHUNKS_ASSET_NAME, + SIX_CHUNKS_ASSET_NAME, + TEN_CHUNKS_ASSET_NAME, + ] { + asset_configs.push(AssetConfig::File { + path: asset_name.to_string(), + content_type: Some("application/octet-stream".to_string()), headers: get_asset_headers(vec![( "cache-control".to_string(), "public, no-cache, no-store".to_string(), @@ -57,13 +106,9 @@ fn certify_all_assets() { fallback_for: vec![], aliased_by: vec![], encodings: vec![], - }, - ]; - - let assets = vec![ - Asset::new("index.html", b"Hello, world!"), - Asset::new("asset_206", ASSET_206_BODY), - ]; + }); + assets.push(Asset::new(asset_name, long_asset_body(asset_name))); + } ASSET_ROUTER.with_borrow_mut(|asset_router| { if let Err(err) = asset_router.certify_assets(assets, asset_configs) { @@ -76,51 +121,13 @@ fn certify_all_assets() { fn serve_asset(req: &HttpRequest) -> HttpResponse<'static> { ASSET_ROUTER.with_borrow(|asset_router| { - if let Ok((mut response, witness, expr_path)) = asset_router.serve_asset(req) { - add_certificate_header(&mut response, &witness, &expr_path); - // 'asset_206' is split into two chunks, to test "chunk-wise" serving of assets. - if req.url().contains("asset_206") { - const FIRST_CHUNK_LEN: usize = 42; - let mut builder = HttpResponse::builder() - .with_status_code(206) - .with_headers( - response - .headers() - .iter() - .filter(|(name, _)| name.to_ascii_lowercase() != "content-length") - .map(|(name, value)| (name.into(), value.into())) - .collect::>(), - ) - .with_upgrade(response.upgrade().unwrap_or(false)); - let range_header = ("Range".to_string(), format!("bytes={}-", FIRST_CHUNK_LEN)); - let range_header_lowercase = - ("range".to_string(), format!("bytes={}-", FIRST_CHUNK_LEN)); - let content_range = if req.headers().contains(&range_header) - || req.headers().contains(&range_header_lowercase) - { - builder = builder.with_body(ASSET_206_BODY[FIRST_CHUNK_LEN..].to_vec()); - format!( - "bytes {}-{}/{}", - FIRST_CHUNK_LEN, - ASSET_206_BODY.len() - 1, - ASSET_206_BODY.len() - ) - } else { - builder = builder.with_body(ASSET_206_BODY[..FIRST_CHUNK_LEN].to_vec()); - format!("bytes 0-{}/{}", FIRST_CHUNK_LEN - 1, ASSET_206_BODY.len()) - }; - let mut response_206 = builder.build(); - response_206.add_header(( - "Content-Length".to_string(), - response_206.body().len().to_string(), - )); - response_206.add_header(("Content-Range".to_string(), content_range)); - response_206 - } else { - response - } + if let Ok(response) = asset_router.serve_asset( + &data_certificate().expect("No data certificate available"), + req, + ) { + response } else { - ic_cdk::trap("Failed to serve asset"); + ic_cdk::trap(&format!("Failed to serve asset for request {:?}", req)); } }) } @@ -140,31 +147,3 @@ fn get_asset_headers(additional_headers: Vec) -> Vec { headers } - -const IC_CERTIFICATE_HEADER: &str = "IC-Certificate"; -fn add_certificate_header(response: &mut HttpResponse, witness: &HashTree, expr_path: &[String]) { - let certified_data = data_certificate().expect("No data certificate available"); - let witness = cbor_encode(witness); - let expr_path = cbor_encode(&expr_path); - - response.add_header(( - IC_CERTIFICATE_HEADER.to_string(), - format!( - "certificate=:{}:, tree=:{}:, expr_path=:{}:, version=2", - BASE64.encode(certified_data), - BASE64.encode(witness), - BASE64.encode(expr_path) - ), - )); -} - -fn cbor_encode(value: &impl Serialize) -> Vec { - let mut serializer = serde_cbor::Serializer::new(Vec::new()); - serializer - .self_describe() - .expect("Failed to self describe CBOR"); - value - .serialize(&mut serializer) - .expect("Failed to serialize value"); - serializer.into_inner() -} diff --git a/packages/ic-http-gateway/Cargo.toml b/packages/ic-http-gateway/Cargo.toml index 41587f6..da43114 100644 --- a/packages/ic-http-gateway/Cargo.toml +++ b/packages/ic-http-gateway/Cargo.toml @@ -39,3 +39,6 @@ ic-response-verification.workspace = true assert_matches.workspace = true pocket-ic.workspace = true tokio.workspace = true +rand_chacha.workspace = true +rstest.workspace = true +sha2.workspace = true diff --git a/packages/ic-http-gateway/src/protocol/handler.rs b/packages/ic-http-gateway/src/protocol/handler.rs index 3cd049c..5e256af 100644 --- a/packages/ic-http-gateway/src/protocol/handler.rs +++ b/packages/ic-http-gateway/src/protocol/handler.rs @@ -28,7 +28,7 @@ fn create_err_response(status_code: StatusCode, msg: &str) -> CanisterResponse { response } -fn convert_request(request: CanisterRequest) -> HttpGatewayResult { +fn convert_request(request: CanisterRequest) -> HttpGatewayResult> { let uri = request.uri(); let mut url = uri.path().to_string(); if let Some(query) = uri.query() { @@ -36,27 +36,29 @@ fn convert_request(request: CanisterRequest) -> HttpGatewayResult { url.push_str(query); } - Ok(HttpRequest { - method: request.method().to_string(), - url, - headers: request - .headers() - .into_iter() - .map(|(name, value)| { - Ok(( - name.to_string(), - value - .to_str() - .map_err(|_| HttpGatewayError::HeaderValueParsingError { - header_name: name.to_string(), - header_value: String::from_utf8_lossy(value.as_bytes()).to_string(), - })? - .to_string(), - )) - }) - .collect::>>()?, - body: request.body().to_vec(), - }) + Ok(HttpRequest::builder() + .with_method(request.method().to_string()) + .with_url(url) + .with_headers( + request + .headers() + .into_iter() + .map(|(name, value)| { + Ok(( + name.to_string(), + value + .to_str() + .map_err(|_| HttpGatewayError::HeaderValueParsingError { + header_name: name.to_string(), + header_value: String::from_utf8_lossy(value.as_bytes()).to_string(), + })? + .to_string(), + )) + }) + .collect::>>()?, + ) + .with_body(request.body().to_vec()) + .build()) } pub async fn process_request( @@ -85,7 +87,7 @@ pub async fn process_request( let canister = HttpRequestCanister::create(agent, canister_id); let mut is_range_request = false; let header_fields = http_request - .headers + .headers() .iter() .filter(|(name, _)| name != "x-request-id") .map(|(name, value)| { @@ -110,10 +112,10 @@ pub async fn process_request( let query_result = canister .http_request_custom( - &http_request.method, - &http_request.url, + &http_request.method(), + &http_request.url(), header_fields.clone(), - &http_request.body, + &http_request.body(), Some(&u16::from(MAX_VERIFICATION_VERSION)), ) .call() @@ -137,10 +139,10 @@ pub async fn process_request( let agent_response = if is_update_call { let update_result = canister .http_request_update_custom( - &http_request.method, - &http_request.url, + &http_request.method(), + &http_request.url(), header_fields.clone(), - &http_request.body, + &http_request.body(), ) .call_and_wait() .await; @@ -180,9 +182,7 @@ pub async fn process_request( }; // There is no need to verify the response if the request was upgraded to an update call. - // Also, we temporarily skip verification for partial (206) responses. - // TODO: re-enable the verification of 206-responses once canister-code supports it. - let validation_info = if !is_update_call && agent_response.status_code != 206 { + let validation_info = if !is_update_call { // At the moment verification is only performed if the response is not using a streaming // strategy. Performing verification for those requests would required to join all the chunks // and this could cause memory issues and possibly create DOS attack vectors. @@ -195,16 +195,17 @@ pub async fn process_request( agent, &canister_id, http_request.clone(), - HttpResponse { - status_code: agent_response.status_code, - headers: agent_response - .headers - .iter() - .map(|HeaderField(k, v)| (k.to_string(), v.to_string())) - .collect(), - body, - upgrade: None, - }, + HttpResponse::builder() + .with_status_code(agent_response.status_code) + .with_headers( + agent_response + .headers + .iter() + .map(|HeaderField(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + .with_body(body) + .build(), skip_verification, ); @@ -446,18 +447,16 @@ mod tests { assert_eq!( http_request, - HttpRequest { - method: "GET".to_string(), - url: "/foo/bar/baz?q=hello+world&t=1".to_string(), - headers: vec![ + HttpRequest::get("/foo/bar/baz?q=hello+world&t=1") + .with_headers(vec![ ("accept".to_string(), "text/html".to_string()), ( "accept-encoding".to_string(), "gzip, deflate, br, zstd".to_string() ), - ], - body: b"body".to_vec(), - } + ]) + .with_body(b"body".to_vec()) + .build() ); } } diff --git a/packages/ic-http-gateway/src/response/response_handler.rs b/packages/ic-http-gateway/src/response/response_handler.rs index ba418b3..1b1dd22 100644 --- a/packages/ic-http-gateway/src/response/response_handler.rs +++ b/packages/ic-http-gateway/src/response/response_handler.rs @@ -127,8 +127,8 @@ fn create_stream( } #[derive(Clone, Debug)] -struct StreamState { - pub http_request: HttpRequest, +struct StreamState<'a> { + pub http_request: HttpRequest<'a>, pub canister_id: Principal, pub total_length: usize, pub fetched_length: usize, @@ -136,7 +136,7 @@ struct StreamState { pub async fn get_206_stream_response_body_and_total_length( agent: &Agent, - http_request: HttpRequest, + http_request: HttpRequest<'static>, canister_id: Principal, response_headers: &Vec>, response_206_body: HttpGatewayResponseBody, @@ -221,11 +221,11 @@ fn get_content_range_values( parse_content_range_header_str(&str_value) } -fn get_stream_state( - http_request: HttpRequest, +fn get_stream_state<'a>( + http_request: HttpRequest<'a>, canister_id: Principal, response_headers: &Vec>, -) -> Result { +) -> Result, AgentError> { let range_values = get_content_range_values(response_headers)?; Ok(StreamState { @@ -241,7 +241,7 @@ fn get_stream_state( fn create_206_body_stream( agent: Agent, - stream_state: StreamState, + stream_state: StreamState<'static>, initial_body: Vec, ) -> ResponseBodyStream { let chunks_stream = create_206_stream(agent, Some(stream_state)) @@ -268,7 +268,7 @@ fn create_206_stream( }; let canister = HttpRequestCanister::create(&agent, stream_state.canister_id); let next_chunk_begin = stream_state.fetched_length; - let mut updated_headers = stream_state.http_request.headers.clone(); + let mut updated_headers = stream_state.http_request.headers().to_vec(); updated_headers.push(("Range".to_string(), format!("bytes={}-", next_chunk_begin))); let headers = updated_headers .iter() @@ -277,10 +277,10 @@ fn create_206_stream( .into_iter(); let query_result = canister .http_request( - &stream_state.http_request.method, - &stream_state.http_request.url, + &stream_state.http_request.method(), + &stream_state.http_request.url(), headers, - &stream_state.http_request.body, + &stream_state.http_request.body(), Some(&u16::from(MAX_VERIFICATION_VERSION)), ) .call() @@ -365,12 +365,10 @@ mod tests { #[test] fn should_get_stream_state() { - let http_request = HttpRequest { - method: "GET".to_string(), - url: "http://example.com/some_file".to_string(), - headers: vec![("Xyz".to_string(), "some value".to_string())], - body: vec![42], - }; + let http_request = HttpRequest::get("http://example.com/some_file") + .with_headers(vec![("Xyz".to_string(), "some value".to_string())]) + .with_body(vec![42]) + .build(); let canister_id = Principal::from_slice(&[1, 2, 3, 4]); let response_headers = vec![HeaderField( Cow::from("Content-Range"), @@ -386,12 +384,10 @@ mod tests { #[test] fn should_fail_get_stream_state_without_content_range_header() { - let http_request = HttpRequest { - method: "GET".to_string(), - url: "http://example.com/some_file".to_string(), - headers: vec![("Xyz".to_string(), "some value".to_string())], - body: vec![42], - }; + let http_request = HttpRequest::get("http://example.com/some_file") + .with_headers(vec![("Xyz".to_string(), "some value".to_string())]) + .with_body(vec![42]) + .build(); let canister_id = Principal::from_slice(&[1, 2, 3, 4]); let response_headers = vec![HeaderField( Cow::from("other header"), @@ -403,12 +399,10 @@ mod tests { #[test] fn should_fail_get_stream_state_with_malformed_content_range_header() { - let http_request = HttpRequest { - method: "GET".to_string(), - url: "http://example.com/some_file".to_string(), - headers: vec![("Xyz".to_string(), "some value".to_string())], - body: vec![42], - }; + let http_request = HttpRequest::get("http://example.com/some_file") + .with_headers(vec![("Xyz".to_string(), "some value".to_string())]) + .with_body(vec![42]) + .build(); let canister_id = Principal::from_slice(&[1, 2, 3, 4]); let response_headers = vec![HeaderField( Cow::from("Content-Range"), diff --git a/packages/ic-http-gateway/tests/range_request_stream.rs b/packages/ic-http-gateway/tests/range_request_stream.rs index d88a93a..10eaf28 100644 --- a/packages/ic-http-gateway/tests/range_request_stream.rs +++ b/packages/ic-http-gateway/tests/range_request_stream.rs @@ -1,16 +1,56 @@ use http::Request; use http_body_util::BodyExt; +use ic_agent::hash_tree::Hash; use ic_agent::Agent; use ic_http_gateway::{HttpGatewayClient, HttpGatewayRequestArgs, HttpGatewayResponseMetadata}; use pocket_ic::PocketIcBuilder; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use rstest::*; +use sha2::{Digest, Sha256}; +use std::cmp::min; mod utils; -const EXPECTED_ASSET_BODY: &[u8; 64] = - b"Some asset that returns a 206-response"; +const ASSET_CHUNK_SIZE: usize = 2_000_000; + +const ONE_CHUNK_ASSET_LEN: usize = ASSET_CHUNK_SIZE; +const TWO_CHUNKS_ASSET_LEN: usize = ASSET_CHUNK_SIZE + 1; +const SIX_CHUNKS_ASSET_LEN: usize = 5 * ASSET_CHUNK_SIZE + 12; +const TEN_CHUNKS_ASSET_LEN: usize = 10 * ASSET_CHUNK_SIZE; + +const ONE_CHUNK_ASSET_NAME: &str = "long_asset_one_chunk"; +const TWO_CHUNKS_ASSET_NAME: &str = "long_asset_two_chunks"; +const SIX_CHUNKS_ASSET_NAME: &str = "long_asset_six_chunks"; +const TEN_CHUNKS_ASSET_NAME: &str = "long_asset_ten_chunks"; + +pub fn hash(data: T) -> Hash +where + T: AsRef<[u8]>, +{ + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().into() +} + +fn long_asset_body(asset_name: &str) -> Vec { + let asset_length = match asset_name { + ONE_CHUNK_ASSET_NAME => ONE_CHUNK_ASSET_LEN, + TWO_CHUNKS_ASSET_NAME => TWO_CHUNKS_ASSET_LEN, + SIX_CHUNKS_ASSET_NAME => SIX_CHUNKS_ASSET_LEN, + TEN_CHUNKS_ASSET_NAME => TEN_CHUNKS_ASSET_LEN, + _ => ASSET_CHUNK_SIZE * 3 + 1, + }; + let mut rng = ChaCha20Rng::from_seed(hash(asset_name)); + let mut body = vec![0u8; asset_length]; + rng.fill_bytes(&mut body); + body +} -#[test] -fn test_long_asset_yields_range_request_stream() { +#[rstest] +#[case("index.html")] +#[case(TWO_CHUNKS_ASSET_NAME)] +fn test_long_asset_yields_range_request_stream(#[case] asset_name: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); @@ -39,11 +79,19 @@ fn test_long_asset_yields_range_request_stream() { http_gateway .request(HttpGatewayRequestArgs { canister_id, - canister_request: Request::builder().uri("/asset_206").body(vec![]).unwrap(), + canister_request: Request::builder() + .uri(format!("/{asset_name}")) + .body(vec![]) + .unwrap(), }) .send() .await }); + println!( + "--- response for {} is {:?}", + asset_name, + response.canister_response.headers() + ); let response_headers = response .canister_response @@ -72,6 +120,8 @@ fn test_long_asset_yields_range_request_stream() { .cloned() // To convert from iterator of references to an iterator of owned values .collect(); + let expected_body = long_asset_body(asset_name); + assert_eq!( certified_headers, vec![ @@ -84,8 +134,8 @@ fn test_long_asset_yields_range_request_stream() { ("cross-origin-embedder-policy", "require-corp"), ("cross-origin-opener-policy", "same-origin"), ("cache-control", "public, no-cache, no-store"), - ("content-type", "text/html"), - ("content-length", EXPECTED_ASSET_BODY.len().to_string().as_str()), + ("content-type", "application/octet-stream"), + ("content-length", expected_body.len().to_string().as_str()), ] ); @@ -99,7 +149,7 @@ fn test_long_asset_yields_range_request_stream() { .to_bytes() .to_vec(); - assert_eq!(body, EXPECTED_ASSET_BODY); + assert_eq!(body, expected_body); }); assert_response_metadata( @@ -112,8 +162,10 @@ fn test_long_asset_yields_range_request_stream() { ); } -#[test] -fn test_range_request_yields_range_response() { +#[rstest] +#[case(ONE_CHUNK_ASSET_NAME)] +#[case(TWO_CHUNKS_ASSET_NAME)] +fn test_range_request_yields_range_response(#[case] asset_name: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); @@ -138,14 +190,13 @@ fn test_range_request_yields_range_response() { .build() .unwrap(); - const FIRST_CHUNK_LEN: usize = 42; let response = rt.block_on(async { http_gateway .request(HttpGatewayRequestArgs { canister_id, canister_request: Request::builder() - .uri("/asset_206") - .header("Range", format!("bytes={}-", FIRST_CHUNK_LEN)) + .uri(format!("/{asset_name}")) + .header("Range", format!("bytes={}-", ASSET_CHUNK_SIZE)) .body(vec![]) .unwrap(), }) @@ -153,7 +204,8 @@ fn test_range_request_yields_range_response() { .await }); - let expected_response_body = &EXPECTED_ASSET_BODY[FIRST_CHUNK_LEN..]; + let expected_full_body = long_asset_body(asset_name); + let expected_response_body = &expected_full_body[ASSET_CHUNK_SIZE..]; let response_headers = response .canister_response .headers() @@ -193,13 +245,13 @@ fn test_range_request_yields_range_response() { ("cross-origin-embedder-policy", "require-corp"), ("cross-origin-opener-policy", "same-origin"), ("cache-control", "public, no-cache, no-store"), - ("content-type", "text/html"), + ("content-type", "application/octet-stream"), ("content-length", expected_response_body.len().to_string().as_str()), ("content-range", &format!( "bytes {}-{}/{}", - FIRST_CHUNK_LEN, - EXPECTED_ASSET_BODY.len() - 1, - EXPECTED_ASSET_BODY.len() + ASSET_CHUNK_SIZE, + min(expected_full_body.len(), 2*ASSET_CHUNK_SIZE) - 1, + expected_full_body.len() )) ] ); From 7d8db829a0be073d0e539fbd23c708b749a4a3c8 Mon Sep 17 00:00:00 2001 From: "przydatek@github.com" Date: Fri, 25 Oct 2024 10:17:01 +0200 Subject: [PATCH 2/4] fix: enable validation of individual chunks --- Cargo.lock | 28 +-- Cargo.toml | 9 +- .../canister/src/custom_assets/Cargo.toml | 2 + .../canister/src/custom_assets/src/lib.rs | 107 ++++++++++- .../ic-http-gateway/src/protocol/handler.rs | 85 +++++---- .../src/response/response_handler.rs | 71 +++++-- .../tests/range_request_stream.rs | 173 ++++++++++++++++-- packages/ic-http-gateway/tests/utils/mod.rs | 4 +- 8 files changed, 392 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d418b15..94260d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -902,14 +902,16 @@ version = "0.1.0" dependencies = [ "base64 0.22.1", "candid", + "http 1.1.0", "ic-asset-certification", "ic-cdk", "ic-cdk-macros", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "ic-http-certification", "include_dir", "lazy_static", "rand_chacha", + "regex", "serde", "serde_cbor", "sha2 0.10.8", @@ -1043,11 +1045,11 @@ dependencies = [ [[package]] name = "ic-asset-certification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "globset", "http 0.2.12", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "ic-http-certification", "regex", "thiserror", @@ -1056,10 +1058,10 @@ dependencies = [ [[package]] name = "ic-cbor" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "candid", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "leb128", "nom", "thiserror", @@ -1095,12 +1097,12 @@ dependencies = [ [[package]] name = "ic-certificate-verification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "cached 0.47.0", "candid", "ic-cbor", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "lazy_static", "leb128", "miracl_core_bls12381", @@ -1125,7 +1127,7 @@ dependencies = [ [[package]] name = "ic-certification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "hex", "serde", @@ -1136,12 +1138,12 @@ dependencies = [ [[package]] name = "ic-http-certification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "base64 0.21.7", "candid", "http 0.2.12", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "ic-representation-independent-hash", "serde", "serde_cbor", @@ -1176,7 +1178,7 @@ dependencies = [ [[package]] name = "ic-representation-independent-hash" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "leb128", "sha2 0.10.8", @@ -1185,7 +1187,7 @@ dependencies = [ [[package]] name = "ic-response-verification" version = "2.6.0" -source = "git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1#51f4066fbb98769bae9ac2307e9ccdb2c88769d1" +source = "git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f#dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" dependencies = [ "base64 0.21.7", "candid", @@ -1194,7 +1196,7 @@ dependencies = [ "http 0.2.12", "ic-cbor", "ic-certificate-verification", - "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=51f4066fbb98769bae9ac2307e9ccdb2c88769d1)", + "ic-certification 2.6.0 (git+https://github.com/dfinity/response-verification?rev=dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f)", "ic-http-certification", "ic-representation-independent-hash", "leb128", diff --git a/Cargo.toml b/Cargo.toml index c861b3e..debb68d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,9 @@ pocket-ic = "=3.0.0" assert_matches = "1.5" rstest = "0.18" - -ic-response-verification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } -ic-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } -ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } -ic-asset-certification = { git = "https://github.com/dfinity/response-verification", rev="51f4066fbb98769bae9ac2307e9ccdb2c88769d1" } +ic-response-verification = { git = "https://github.com/dfinity/response-verification", rev="dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" } +ic-certification = { git = "https://github.com/dfinity/response-verification", rev="dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" } +ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev="dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" } +ic-asset-certification = { git = "https://github.com/dfinity/response-verification", rev="dcbd7d471e1a5f95d8b1e8a66c4a35fd2a68268f" } ic-http-gateway = { path = "./packages/ic-http-gateway", version = "0.0.0" } diff --git a/examples/http-gateway/canister/src/custom_assets/Cargo.toml b/examples/http-gateway/canister/src/custom_assets/Cargo.toml index 34deef2..5113373 100644 --- a/examples/http-gateway/canister/src/custom_assets/Cargo.toml +++ b/examples/http-gateway/canister/src/custom_assets/Cargo.toml @@ -8,8 +8,10 @@ crate-type = ["cdylib"] [dependencies] candid.workspace = true +http.workspace = true ic-cdk.workspace = true ic-cdk-macros.workspace = true +regex.workspace = true serde.workspace = true serde_cbor.workspace = true sha2.workspace = true diff --git a/examples/http-gateway/canister/src/custom_assets/src/lib.rs b/examples/http-gateway/canister/src/custom_assets/src/lib.rs index 8c6df50..4d85e79 100644 --- a/examples/http-gateway/canister/src/custom_assets/src/lib.rs +++ b/examples/http-gateway/canister/src/custom_assets/src/lib.rs @@ -3,7 +3,7 @@ use ic_cdk::{ api::{data_certificate, set_certified_data}, *, }; -use ic_http_certification::{HeaderField, HttpRequest, HttpResponse}; +use ic_http_certification::{HeaderField, HttpRequest, HttpResponse, HttpResponseBuilder}; use rand_chacha::rand_core::{RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; use std::cell::RefCell; @@ -20,14 +20,103 @@ fn post_upgrade() { #[query] fn http_request(req: HttpRequest) -> HttpResponse { - ic_cdk::println!("*** serving request ***: {:?}", req); - let resp = serve_asset(&req); - ic_cdk::println!( - "\n+++ returning resp: {}, {:?}", - resp.status_code(), - resp.headers() - ); - resp + let mut response = serve_asset(&req); + if let Some(index) = chunk_corruption_requested(&req) { + let current_chunk = current_chunk_index(&response); + if current_chunk == index { + // Create a response with a corrupted body. + response = HttpResponseBuilder::new() + .with_status_code(response.status_code()) + .with_headers(response.headers().to_vec()) + .with_upgrade(response.upgrade().unwrap_or(false)) + .with_body({ + let mut body = response.body().to_vec(); + body[0] += 1; + body + }) + .build(); + } + } + if let Some(index) = cert_corruption_requested(&req) { + let current_chunk = current_chunk_index(&response); + if current_chunk == index { + let body = response.body().to_owned(); + // Create a response with a corrupted certificate. + response = HttpResponseBuilder::new() + .with_status_code(response.status_code()) + .with_upgrade(response.upgrade().unwrap_or(false)) + .with_body(body) + .with_headers({ + let mut headers = response.headers().to_owned(); + for (key, value) in headers.iter_mut() { + if key == "IC-Certificate" { + value.insert(15, char::from(42)); + } + } + headers.to_vec() + }) + .build(); + } + } + response +} + +fn current_chunk_index(resp: &HttpResponse) -> usize { + if let Some(content_range_header_value) = get_header_value(resp.headers(), "Content-Range") { + get_content_range_begin(&content_range_header_value) / ASSET_CHUNK_SIZE + } else { + // Not a range-response, the asset is a single chunk + 0 + } +} + +fn chunk_corruption_requested(req: &HttpRequest) -> Option { + if let Some(corrupted_chunk_index) = get_header_value(req.headers(), "Test-CorruptedChunkIndex") + { + Some( + corrupted_chunk_index + .parse() + .expect("invalid index of chunk to corrupt"), + ) + } else { + None + } +} + +fn cert_corruption_requested(req: &HttpRequest) -> Option { + if let Some(corrupted_cert_chunk_index) = + get_header_value(req.headers(), "Test-CorruptedCertificate") + { + Some( + corrupted_cert_chunk_index + .parse() + .expect("invalid index of chunk to corrupt the certificate"), + ) + } else { + None + } +} + +fn get_header_value(headers: &[HeaderField], header_name: &str) -> Option { + for (name, value) in headers.iter() { + if name.to_lowercase().eq(&header_name.to_lowercase()) { + return Some(value.to_string()); + } + } + None +} + +fn get_content_range_begin(content_range_header_value: &str) -> usize { + // expected format: `bytes 21010-47021/47022` + let re = regex::Regex::new(r"bytes\s+(\d+)-(\d+)/(\d+)").expect("invalid RE"); + let caps = re + .captures(content_range_header_value) + .expect("malformed Content-Range header"); + caps.get(1) + .expect("missing range-begin") + .as_str() + .parse() + .expect("malformed range-begin") } thread_local! { diff --git a/packages/ic-http-gateway/src/protocol/handler.rs b/packages/ic-http-gateway/src/protocol/handler.rs index 5e256af..4a603e8 100644 --- a/packages/ic-http-gateway/src/protocol/handler.rs +++ b/packages/ic-http-gateway/src/protocol/handler.rs @@ -112,10 +112,10 @@ pub async fn process_request( let query_result = canister .http_request_custom( - &http_request.method(), - &http_request.url(), + http_request.method(), + http_request.url(), header_fields.clone(), - &http_request.body(), + http_request.body(), Some(&u16::from(MAX_VERIFICATION_VERSION)), ) .call() @@ -139,10 +139,10 @@ pub async fn process_request( let agent_response = if is_update_call { let update_result = canister .http_request_update_custom( - &http_request.method(), - &http_request.url(), + http_request.method(), + http_request.url(), header_fields.clone(), - &http_request.body(), + http_request.body(), ) .call_and_wait() .await; @@ -184,28 +184,30 @@ pub async fn process_request( // There is no need to verify the response if the request was upgraded to an update call. let validation_info = if !is_update_call { // At the moment verification is only performed if the response is not using a streaming - // strategy. Performing verification for those requests would required to join all the chunks + // strategy. Performing verification for those requests would require to join all the chunks // and this could cause memory issues and possibly create DOS attack vectors. match &response_body { Either::Right(body) => { // this unwrap should never panic because `Either::Right` will always have a full body let body = body.clone().collect().await.unwrap().to_bytes().to_vec(); + let response = HttpResponse::builder() + .with_status_code(agent_response.status_code) + .with_headers( + agent_response + .headers + .iter() + .map(|HeaderField(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + .with_body(body) + .build(); + let validation_result = validate( agent, &canister_id, http_request.clone(), - HttpResponse::builder() - .with_status_code(agent_response.status_code) - .with_headers( - agent_response - .headers - .iter() - .map(|HeaderField(k, v)| (k.to_string(), v.to_string())) - .collect(), - ) - .with_body(body) - .build(), + response, skip_verification, ); @@ -254,21 +256,10 @@ pub async fn process_request( let mut response_builder = Response::builder().status(status_code); match &validation_info { // if there is no validation info, that means we've skipped verification, - // this should only happen for raw domains, or for responses that are too - // large to verify, return response as-is + // this should only happen for raw domains. None => { for HeaderField(name, value) in &agent_response.headers { - // If the request is not a range-request, do not copy "Content-Range" - // and "Content-Length" headers, as clients obtain the full asset - // via a streaming response. - if !is_range_request - && (name.eq_ignore_ascii_case(http_header::CONTENT_RANGE.as_ref()) - || name.eq_ignore_ascii_case(http_header::CONTENT_LENGTH.as_ref())) - { - // skip copying - } else { - response_builder = response_builder.header(name.as_ref(), value.as_ref()); - } + response_builder = response_builder.header(name.as_ref(), value.as_ref()); } } @@ -301,15 +292,39 @@ pub async fn process_request( // assume the developer knows what they're doing and return the response as-is None => { for HeaderField(name, value) in &agent_response.headers { - response_builder = - response_builder.header(name.as_ref(), value.as_ref()); + // If the request is not a range-request, but got range-response, + // do not copy "Content-Range" and "Content-Length" headers, + // as clients obtain the full asset via a streaming response. + if !is_range_request + && status_code == 206 + && (name.eq_ignore_ascii_case(http_header::CONTENT_RANGE.as_ref()) + || name + .eq_ignore_ascii_case(http_header::CONTENT_LENGTH.as_ref())) + { + // skip copying + } else { + response_builder = + response_builder.header(name.as_ref(), value.as_ref()); + } } } // if there is a response, the canister has decided to certify some (but not necessarily all) headers, // return only the certified headers Some(certified_http_response) => { for (name, value) in &certified_http_response.headers { - response_builder = response_builder.header(name, value); + // If the request is not a range-request, but got range-response, + // do not copy "Content-Range" and "Content-Length" headers, + // as clients obtain the full asset via a streaming response. + if !is_range_request + && status_code == 206 + && (name.eq_ignore_ascii_case(http_header::CONTENT_RANGE.as_ref()) + || name + .eq_ignore_ascii_case(http_header::CONTENT_LENGTH.as_ref())) + { + // skip copying + } else { + response_builder = response_builder.header(name, value); + } } } } @@ -327,6 +342,7 @@ pub async fn process_request( canister_id, &agent_response.headers, response_body, + skip_verification, ) .await { @@ -349,6 +365,7 @@ pub async fn process_request( }; response_builder = response_builder.header(http_header::CONTENT_LENGTH, content_length.to_string()); + response_builder = response_builder.status(200); stream_response_body } else { response_body diff --git a/packages/ic-http-gateway/src/response/response_handler.rs b/packages/ic-http-gateway/src/response/response_handler.rs index 1b1dd22..65d81d3 100644 --- a/packages/ic-http-gateway/src/response/response_handler.rs +++ b/packages/ic-http-gateway/src/response/response_handler.rs @@ -1,3 +1,4 @@ +use crate::protocol::validate; use crate::{HttpGatewayResponseBody, ResponseBodyStream}; use bytes::Bytes; use candid::Principal; @@ -5,7 +6,7 @@ use futures::{stream, Stream, StreamExt, TryStreamExt}; use http_body::Frame; use http_body_util::{BodyExt, Full}; use ic_agent::{Agent, AgentError}; -use ic_http_certification::HttpRequest; +use ic_http_certification::{HttpRequest, HttpResponse}; use ic_response_verification::MAX_VERIFICATION_VERSION; use ic_utils::interfaces::http_request::HeaderField; use ic_utils::{ @@ -132,6 +133,7 @@ struct StreamState<'a> { pub canister_id: Principal, pub total_length: usize, pub fetched_length: usize, + pub skip_verification: bool, } pub async fn get_206_stream_response_body_and_total_length( @@ -140,6 +142,7 @@ pub async fn get_206_stream_response_body_and_total_length( canister_id: Principal, response_headers: &Vec>, response_206_body: HttpGatewayResponseBody, + skip_verification: bool, ) -> Result<(HttpGatewayResponseBody, usize), AgentError> { let HttpGatewayResponseBody::Right(body) = response_206_body else { return Err(AgentError::InvalidHttpResponse( @@ -153,11 +156,16 @@ pub async fn get_206_stream_response_body_and_total_length( .expect("missing streamed chunk body") .to_bytes() .to_vec(); - let stream_state = get_stream_state(http_request, canister_id, response_headers)?; + let stream_state = get_stream_state( + http_request, + canister_id, + response_headers, + skip_verification, + )?; let content_length = stream_state.total_length; let body_stream = create_206_body_stream(agent.clone(), stream_state, streamed_body); - return Ok((HttpGatewayResponseBody::Left(body_stream), content_length)); + Ok((HttpGatewayResponseBody::Left(body_stream), content_length)) } #[derive(Debug)] @@ -194,11 +202,11 @@ fn parse_content_range_header_str(str_value: &str) -> Result( http_request: HttpRequest<'a>, canister_id: Principal, response_headers: &Vec>, + skip_verification: bool, ) -> Result, AgentError> { let range_values = get_content_range_values(response_headers)?; @@ -236,6 +245,7 @@ fn get_stream_state<'a>( .range_end .saturating_sub(range_values.range_begin) + 1, + skip_verification, }) } @@ -295,7 +305,37 @@ fn create_206_stream( .saturating_sub(range_values.range_begin) + 1; let current_fetched_length = stream_state.fetched_length + chunk_length; - // TODO: verify the range response, once we can prepare certified chunks + // Verify the chunk from the range response. + if agent_response.streaming_strategy.is_some() { + return Err(AgentError::InvalidHttpResponse( + "unexpected StreamingStrategy".to_string(), + )); + } + let response = HttpResponse::builder() + .with_status_code(agent_response.status_code) + .with_headers( + agent_response + .headers + .iter() + .map(|HeaderField(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + .with_body(agent_response.body.clone()) + .build(); + let validation_result = validate( + &agent, + &stream_state.canister_id, + stream_state.http_request.clone(), + response, + stream_state.skip_verification, + ); + + if let Err(e) = validation_result { + return Err(AgentError::InvalidHttpResponse(format!( + "CertificateVerificationFailed for a chunk starting at {}, error: {}", + stream_state.fetched_length, e + ))); + } let maybe_new_state = if current_fetched_length < stream_state.total_length { Some(StreamState { fetched_length: current_fetched_length, @@ -340,7 +380,7 @@ mod tests { for v in header_values { let input = format!("bytes {}-{}/{}", v.range_begin, v.range_end, v.total_length); let result = parse_content_range_header_str(&input); - let output = result.expect(&format!("failed parsing '{}'", input)); + let output = result.unwrap_or_else(|_| panic!("failed parsing '{}'", input)); assert_eq!(v.range_begin, output.range_begin); assert_eq!(v.range_end, output.range_end); assert_eq!(v.total_length, output.total_length); @@ -358,7 +398,7 @@ mod tests { "bytes dead-beef/123456", ]; for input in malformed_inputs { - let result = parse_content_range_header_str(&input); + let result = parse_content_range_header_str(input); assert_matches!(result, Err(e) if format!("{}", e).contains("malformed Content-Range header")); } } @@ -374,12 +414,19 @@ mod tests { Cow::from("Content-Range"), Cow::from("bytes 2-4/10"), // fetched 3 bytes, total length is 10 )]; - let state = get_stream_state(http_request.clone(), canister_id, &response_headers) - .expect("failed constructing StreamState"); + let skip_verification = false; + let state = get_stream_state( + http_request.clone(), + canister_id, + &response_headers, + skip_verification, + ) + .expect("failed constructing StreamState"); assert_eq!(state.http_request, http_request); assert_eq!(state.canister_id, canister_id); assert_eq!(state.fetched_length, 3); assert_eq!(state.total_length, 10); + assert_eq!(state.skip_verification, skip_verification); } #[test] @@ -393,7 +440,7 @@ mod tests { Cow::from("other header"), Cow::from("other value"), )]; - let result = get_stream_state(http_request, canister_id, &response_headers); + let result = get_stream_state(http_request, canister_id, &response_headers, false); assert_matches!(result, Err(e) if format!("{}", e).contains("missing Content-Range header")); } @@ -408,7 +455,7 @@ mod tests { Cow::from("Content-Range"), Cow::from("bytes 42/10"), )]; - let result = get_stream_state(http_request, canister_id, &response_headers); + let result = get_stream_state(http_request, canister_id, &response_headers, false); assert_matches!(result, Err(e) if format!("{}", e).contains("malformed Content-Range header")); } } diff --git a/packages/ic-http-gateway/tests/range_request_stream.rs b/packages/ic-http-gateway/tests/range_request_stream.rs index 10eaf28..f3dbd6e 100644 --- a/packages/ic-http-gateway/tests/range_request_stream.rs +++ b/packages/ic-http-gateway/tests/range_request_stream.rs @@ -1,3 +1,4 @@ +use assert_matches::assert_matches; use http::Request; use http_body_util::BodyExt; use ic_agent::hash_tree::Hash; @@ -48,9 +49,10 @@ fn long_asset_body(asset_name: &str) -> Vec { } #[rstest] -#[case("index.html")] #[case(TWO_CHUNKS_ASSET_NAME)] -fn test_long_asset_yields_range_request_stream(#[case] asset_name: &str) { +#[case(SIX_CHUNKS_ASSET_NAME)] +#[case(TEN_CHUNKS_ASSET_NAME)] +fn test_long_asset_request_yields_entire_asset(#[case] asset_name: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); @@ -87,11 +89,6 @@ fn test_long_asset_yields_range_request_stream(#[case] asset_name: &str) { .send() .await }); - println!( - "--- response for {} is {:?}", - asset_name, - response.canister_response.headers() - ); let response_headers = response .canister_response @@ -100,7 +97,7 @@ fn test_long_asset_yields_range_request_stream(#[case] asset_name: &str) { .map(|(k, v)| (k.as_str(), v.to_str().unwrap())) .collect::>(); - assert_eq!(response.canister_response.status(), 206); + assert_eq!(response.canister_response.status(), 200); // check that the response contains the certificate headers assert!( @@ -156,15 +153,164 @@ fn test_long_asset_yields_range_request_stream(#[case] asset_name: &str) { response.metadata, HttpGatewayResponseMetadata { upgraded_to_update_call: false, - response_verification_version: None, + response_verification_version: Some(2), internal_error: None, }, ); } #[rstest] -#[case(ONE_CHUNK_ASSET_NAME)] +#[case(TWO_CHUNKS_ASSET_NAME, 0)] +#[case(TWO_CHUNKS_ASSET_NAME, 1)] +#[case(SIX_CHUNKS_ASSET_NAME, 3)] +#[case(SIX_CHUNKS_ASSET_NAME, 5)] +fn test_corrupted_long_asset_request_fails( + #[case] asset_name: &str, + #[case] corrupted_chunk_index: usize, +) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); + + let pic = PocketIcBuilder::new() + .with_nns_subnet() + .with_application_subnet() + .build(); + + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 2_000_000_000_000); + pic.install_canister(canister_id, wasm_bytes, vec![], None); + + let url = pic.auto_progress(); + + let agent = Agent::builder().with_url(url).build().unwrap(); + rt.block_on(async { + agent.fetch_root_key().await.unwrap(); + }); + + let http_gateway = HttpGatewayClient::builder() + .with_agent(agent) + .build() + .unwrap(); + + let response = rt.block_on(async { + http_gateway + .request(HttpGatewayRequestArgs { + canister_id, + canister_request: Request::builder() + .header( + "Test-CorruptedChunkIndex", + corrupted_chunk_index.to_string(), + ) + .uri(format!("/{asset_name}")) + .body(vec![]) + .unwrap(), + }) + .send() + .await + }); + let expected_status = match corrupted_chunk_index { + 0 => 500, + _ => 200, + }; + assert_eq!(response.canister_response.status(), expected_status); + rt.block_on(async { + let body_result = response.canister_response.into_body().collect().await; + if corrupted_chunk_index == 0 { + // If the first chunk is corrupted, the status indicates the failure + // and the full body contains the error message. + let body = body_result.expect("failed getting full body").to_bytes(); + assert_eq!( + body, + "Response verification failed: Invalid response hashes" + ); + } else { + // If the first chunk is ok, but some other chunk is corrupted, the response has 200-status, + // but fetching the full body fails with an error for the corrupted chunk. + assert_matches!(body_result, + Err(e) if e.to_string().contains(&format!( + "CertificateVerificationFailed for a chunk starting at {}", + ASSET_CHUNK_SIZE*corrupted_chunk_index)) + ); + } + }); +} + +#[rstest] +#[case(TWO_CHUNKS_ASSET_NAME, 0)] +#[case(TWO_CHUNKS_ASSET_NAME, 1)] +#[case(SIX_CHUNKS_ASSET_NAME, 3)] +#[case(SIX_CHUNKS_ASSET_NAME, 5)] +fn test_corrupted_chunk_certificate_for_long_asset_request_fails( + #[case] asset_name: &str, + #[case] corrupted_chunk_index: usize, +) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); + + let pic = PocketIcBuilder::new() + .with_nns_subnet() + .with_application_subnet() + .build(); + + let canister_id = pic.create_canister(); + pic.add_cycles(canister_id, 2_000_000_000_000); + pic.install_canister(canister_id, wasm_bytes, vec![], None); + + let url = pic.auto_progress(); + + let agent = Agent::builder().with_url(url).build().unwrap(); + rt.block_on(async { + agent.fetch_root_key().await.unwrap(); + }); + + let http_gateway = HttpGatewayClient::builder() + .with_agent(agent) + .build() + .unwrap(); + + let response = rt.block_on(async { + http_gateway + .request(HttpGatewayRequestArgs { + canister_id, + canister_request: Request::builder() + .header( + "Test-CorruptedCertificate", + corrupted_chunk_index.to_string(), + ) + .uri(format!("/{asset_name}")) + .body(vec![]) + .unwrap(), + }) + .send() + .await + }); + let expected_status = match corrupted_chunk_index { + 0 => 500, + _ => 200, + }; + assert_eq!(response.canister_response.status(), expected_status); + rt.block_on(async { + let body_result = response.canister_response.into_body().collect().await; + if corrupted_chunk_index == 0 { + // If the first chunk is corrupted, the status indicates the failure + // and the full body contains the error message. + let body = body_result.expect("failed getting full body").to_bytes(); + assert_matches!(String::from_utf8_lossy(&body), s if s.contains("Response verification failed")); + } else { + // If the first chunk is ok, but some other chunk is corrupted, the response has 200-status, + // but fetching the full body fails with an error for the corrupted chunk. + assert_matches!(body_result, + Err(e) if e.to_string().contains(&format!( + "CertificateVerificationFailed for a chunk starting at {}", + ASSET_CHUNK_SIZE*corrupted_chunk_index)) + ); + } + }); +} + +#[rstest] #[case(TWO_CHUNKS_ASSET_NAME)] +#[case(SIX_CHUNKS_ASSET_NAME)] fn test_range_request_yields_range_response(#[case] asset_name: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let wasm_bytes = rt.block_on(async { utils::load_custom_assets_wasm().await }); @@ -205,7 +351,8 @@ fn test_range_request_yields_range_response(#[case] asset_name: &str) { }); let expected_full_body = long_asset_body(asset_name); - let expected_response_body = &expected_full_body[ASSET_CHUNK_SIZE..]; + let expected_response_body = + &expected_full_body[ASSET_CHUNK_SIZE..min(expected_full_body.len(), 2 * ASSET_CHUNK_SIZE)]; let response_headers = response .canister_response .headers() @@ -236,6 +383,7 @@ fn test_range_request_yields_range_response(#[case] asset_name: &str) { assert_eq!( certified_headers, vec![ + ("content-length", expected_response_body.len().to_string().as_str()), ("strict-transport-security", "max-age=31536000; includeSubDomains"), ("x-frame-options", "DENY"), ("x-content-type-options", "nosniff"), @@ -246,7 +394,6 @@ fn test_range_request_yields_range_response(#[case] asset_name: &str) { ("cross-origin-opener-policy", "same-origin"), ("cache-control", "public, no-cache, no-store"), ("content-type", "application/octet-stream"), - ("content-length", expected_response_body.len().to_string().as_str()), ("content-range", &format!( "bytes {}-{}/{}", ASSET_CHUNK_SIZE, @@ -273,7 +420,7 @@ fn test_range_request_yields_range_response(#[case] asset_name: &str) { response.metadata, HttpGatewayResponseMetadata { upgraded_to_update_call: false, - response_verification_version: None, + response_verification_version: Some(2), internal_error: None, }, ); diff --git a/packages/ic-http-gateway/tests/utils/mod.rs b/packages/ic-http-gateway/tests/utils/mod.rs index dea8a60..b482cfd 100644 --- a/packages/ic-http-gateway/tests/utils/mod.rs +++ b/packages/ic-http-gateway/tests/utils/mod.rs @@ -15,7 +15,9 @@ async fn load_wasm(canister: &str) -> Vec { } async fn load_file(file_path: PathBuf) -> Vec { - let mut file = File::open(&file_path).await.unwrap(); + let mut file = File::open(&file_path) + .await + .unwrap_or_else(|_| panic!("error opening file {:?}", file_path)); let mut buffer = Vec::new(); file.read_to_end(&mut buffer).await.unwrap(); From dd48e937a1f0e038c284bfe064c34453ecd9f189 Mon Sep 17 00:00:00 2001 From: "przydatek@github.com" Date: Fri, 25 Oct 2024 10:26:59 +0200 Subject: [PATCH 3/4] fix: update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 34d6663..d8d74b6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -5,6 +5,8 @@ on: branches: - main pull_request: + branches: + - main jobs: build_and_test_rust: From ce50b63a260dd3f355ca6cf7108e23387c85f496 Mon Sep 17 00:00:00 2001 From: "przydatek@github.com" Date: Mon, 28 Oct 2024 17:14:03 +0100 Subject: [PATCH 4/4] fix: undo previous changes --- .github/workflows/build-and-test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index d8d74b6..34d6663 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -5,8 +5,6 @@ on: branches: - main pull_request: - branches: - - main jobs: build_and_test_rust: