From 544d3769d5ee216781c7dbf7788ebae2faf4e2a9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:57:39 +0000 Subject: [PATCH 01/20] build(deps): pin opendal to work around audit failure (#572) * chore: remove opendal audit blocker Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/66b3faa2-10ca-43e3-b2e9-76e8159b3805 Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * chore: restore opendal test with upstream fix Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/7c8d2897-e06a-499e-8ff6-155d16594bab Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --- Cargo.lock | 231 +++++++++++++++++---------------------- crates/s3s-fs/Cargo.toml | 2 +- 2 files changed, 101 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 715a19b7..76d71afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,17 +589,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers", - "tokio", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -741,9 +730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", - "js-sys", "num-traits", - "wasm-bindgen", "windows-link", ] @@ -1359,18 +1346,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.12.1" @@ -1467,15 +1442,6 @@ dependencies = [ "digest 0.11.2", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "http" version = "0.2.12" @@ -1589,7 +1555,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -1795,11 +1760,13 @@ checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "jiff-tzdb-platform", + "js-sys", "log", "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "wasm-bindgen", + "windows-sys 0.60.2", ] [[package]] @@ -1989,6 +1956,15 @@ dependencies = [ "digest 0.11.2", ] +[[package]] +name = "mea" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6747f54621d156e1b47eb6b25f39a941b9fc347f98f67d25d8881ff99e8ed832" +dependencies = [ + "slab", +] + [[package]] name = "memchr" version = "2.8.0" @@ -2109,31 +2085,58 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opendal" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a" +version = "0.56.0" +source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" +dependencies = [ + "opendal-core", + "opendal-service-s3", +] + +[[package]] +name = "opendal-core" +version = "0.56.0" +source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" dependencies = [ "anyhow", - "backon", "base64", "bytes", - "crc32c", "futures", - "getrandom 0.2.17", "http 1.4.0", "http-body 1.0.1", "jiff", "log", "md-5 0.10.6", + "mea", "percent-encoding", "quick-xml 0.38.4", - "reqsign", - "reqwest 0.12.28", + "reqsign-core", + "reqwest", "serde", "serde_json", "tokio", "url", "uuid", + "web-time", +] + +[[package]] +name = "opendal-service-s3" +version = "0.56.0" +source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" +dependencies = [ + "base64", + "bytes", + "crc32c", + "http 1.4.0", + "log", + "md-5 0.10.6", + "opendal-core", + "quick-xml 0.38.4", + "reqsign-aws-v4", + "reqsign-core", + "reqsign-file-read-tokio", + "serde", + "url", ] [[package]] @@ -2290,9 +2293,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -2360,6 +2363,16 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2390,7 +2403,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand", "ring", "rustc-hash", "rustls", @@ -2439,35 +2452,14 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -2541,73 +2533,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "reqsign" -version = "0.16.5" +name = "reqsign-aws-v4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43451dbf3590a7590684c25fb8d12ecdcc90ed3ac123433e500447c7d77ed701" +checksum = "44eaca382e94505a49f1a4849658d153aebf79d9c1a58e5dd3b10361511e9f43" dependencies = [ "anyhow", - "async-trait", - "base64", - "chrono", + "bytes", "form_urlencoded", - "getrandom 0.2.17", - "hex", - "hmac 0.12.1", - "home", "http 1.4.0", "log", "percent-encoding", - "quick-xml 0.37.5", - "rand 0.8.5", - "reqwest 0.12.28", + "quick-xml 0.39.2", + "reqsign-core", "rust-ini", "serde", "serde_json", + "serde_urlencoded", "sha1 0.10.6", - "sha2 0.10.9", - "tokio", ] [[package]] -name = "reqwest" -version = "0.12.28" +name = "reqsign-core" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "b10302cf0a7d7e7352ba211fc92c3c5bebf1286153e49cc5aa87348078a8e102" dependencies = [ + "anyhow", "base64", "bytes", - "futures-core", - "futures-util", + "form_urlencoded", + "futures", + "hex", + "hmac 0.12.1", "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", + "jiff", "log", "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", + "sha1 0.10.6", + "sha2 0.10.9", + "windows-sys 0.61.2", +] + +[[package]] +name = "reqsign-file-read-tokio" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d89295b3d17abea31851cc8de55d843d89c52132c864963c38d41920613dc5" +dependencies = [ + "anyhow", + "reqsign-core", "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", ] [[package]] @@ -2619,6 +2595,7 @@ dependencies = [ "base64", "bytes", "futures-core", + "futures-util", "http 1.4.0", "http-body 1.0.1", "http-body-util", @@ -2638,12 +2615,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -2712,7 +2691,6 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2770,9 +2748,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", @@ -2896,7 +2874,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "md-5 0.11.0", - "reqwest 0.13.2", + "reqwest", "s3s-test", "tracing", ] @@ -3878,9 +3856,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -3930,15 +3908,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "winapi-util" version = "0.1.11" diff --git a/crates/s3s-fs/Cargo.toml b/crates/s3s-fs/Cargo.toml index 13fad63e..5ce8bcbf 100644 --- a/crates/s3s-fs/Cargo.toml +++ b/crates/s3s-fs/Cargo.toml @@ -62,7 +62,7 @@ aws-sdk-sts = { workspace = true, features = ["behavior-version-latest"] } futures-util.workspace = true hyper = { workspace = true, features = ["http1", "http2"] } hyper-util = { workspace = true, features = ["server-auto", "server-graceful", "http1", "http2", "tokio"] } -opendal = { workspace = true, features = ["services-s3"] } +opendal = { git = "https://github.com/apache/opendal", rev = "8332367dd7629bebc7759a11a5bbbd941dd060e9", default-features = false, features = ["services-s3", "executors-tokio", "reqwest-rustls-tls"] } s3s-aws = { version = "0.14.0-dev", path = "../s3s-aws" } tokio = { workspace = true, features = ["full"] } tracing-subscriber.workspace = true From 3648d00955e8a1bba19a0fd675015e9ac5fab81f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:48:08 +0800 Subject: [PATCH 02/20] build(deps): bump lxml from 5.3.1 to 6.1.0 (#573) Bumps [lxml](https://github.com/lxml/lxml) from 5.3.1 to 6.1.0. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-5.3.1...lxml-6.1.0) --- updated-dependencies: - dependency-name: lxml dependency-version: 6.1.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 77 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 326aca0b..c3d5552b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires-python = ">=3.13" dependencies = [ "beautifulsoup4>=4.12.3", "boto3>=1.35.0", - "lxml>=5.3.0", + "lxml>=6.1.0", "requests>=2.32.3", "typer>=0.12.5", ] diff --git a/uv.lock b/uv.lock index 5bffce19..9a5bd0a3 100644 --- a/uv.lock +++ b/uv.lock @@ -117,27 +117,64 @@ wheels = [ [[package]] name = "lxml" -version = "5.3.1" +version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/f6/c15ca8e5646e937c148e147244817672cf920b56ac0bf2cc1512ae674be8/lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8", size = 3678591, upload-time = "2025-02-10T07:51:41.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/1c/724931daa1ace168e0237b929e44062545bf1551974102a5762c349c668d/lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e", size = 8171881, upload-time = "2025-02-10T07:46:40.653Z" }, - { url = "https://files.pythonhosted.org/packages/67/0c/857b8fb6010c4246e66abeebb8639eaabba60a6d9b7c606554ecc5cbf1ee/lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd", size = 4440394, upload-time = "2025-02-10T07:46:44.037Z" }, - { url = "https://files.pythonhosted.org/packages/61/72/c9e81de6a000f9682ccdd13503db26e973b24c68ac45a7029173237e3eed/lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7", size = 5037860, upload-time = "2025-02-10T07:46:47.919Z" }, - { url = "https://files.pythonhosted.org/packages/24/26/942048c4b14835711b583b48cd7209bd2b5f0b6939ceed2381a494138b14/lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414", size = 4782513, upload-time = "2025-02-10T07:46:50.696Z" }, - { url = "https://files.pythonhosted.org/packages/e2/65/27792339caf00f610cc5be32b940ba1e3009b7054feb0c4527cebac228d4/lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e", size = 5305227, upload-time = "2025-02-10T07:46:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/25f7aa434a4d0d8e8420580af05ea49c3e12db6d297cf5435ac0a054df56/lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1", size = 4829846, upload-time = "2025-02-10T07:46:56.262Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ed/faf235e0792547d24f61ee1448159325448a7e4f2ab706503049d8e5df19/lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5", size = 4949495, upload-time = "2025-02-10T07:46:59.189Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e1/8f572ad9ed6039ba30f26dd4c2c58fb90f79362d2ee35ca3820284767672/lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423", size = 4773415, upload-time = "2025-02-10T07:47:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/a3/75/6b57166b9d1983dac8f28f354e38bff8d6bcab013a241989c4d54c72701b/lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20", size = 5337710, upload-time = "2025-02-10T07:47:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/cc/71/4aa56e2daa83bbcc66ca27b5155be2f900d996f5d0c51078eaaac8df9547/lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8", size = 4897362, upload-time = "2025-02-10T07:47:09.24Z" }, - { url = "https://files.pythonhosted.org/packages/65/10/3fa2da152cd9b49332fd23356ed7643c9b74cad636ddd5b2400a9730d12b/lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9", size = 4977795, upload-time = "2025-02-10T07:47:12.101Z" }, - { url = "https://files.pythonhosted.org/packages/de/d2/e1da0f7b20827e7b0ce934963cb6334c1b02cf1bb4aecd218c4496880cb3/lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c", size = 4858104, upload-time = "2025-02-10T07:47:15.998Z" }, - { url = "https://files.pythonhosted.org/packages/a5/35/063420e1b33d3308f5aa7fcbdd19ef6c036f741c9a7a4bd5dc8032486b27/lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b", size = 5416531, upload-time = "2025-02-10T07:47:19.862Z" }, - { url = "https://files.pythonhosted.org/packages/c3/83/93a6457d291d1e37adfb54df23498101a4701834258c840381dd2f6a030e/lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5", size = 5273040, upload-time = "2025-02-10T07:47:24.29Z" }, - { url = "https://files.pythonhosted.org/packages/39/25/ad4ac8fac488505a2702656550e63c2a8db3a4fd63db82a20dad5689cecb/lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252", size = 5050951, upload-time = "2025-02-10T07:47:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/82/74/f7d223c704c87e44b3d27b5e0dde173a2fcf2e89c0524c8015c2b3554876/lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78", size = 3485357, upload-time = "2025-02-10T07:47:29.738Z" }, - { url = "https://files.pythonhosted.org/packages/80/83/8c54533b3576f4391eebea88454738978669a6cad0d8e23266224007939d/lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332", size = 3814484, upload-time = "2025-02-10T07:47:33.3Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, ] [[package]] @@ -226,7 +263,7 @@ dependencies = [ requires-dist = [ { name = "beautifulsoup4", specifier = ">=4.12.3" }, { name = "boto3", specifier = ">=1.35.0" }, - { name = "lxml", specifier = ">=5.3.0" }, + { name = "lxml", specifier = ">=6.1.0" }, { name = "requests", specifier = ">=2.32.3" }, { name = "typer", specifier = ">=0.12.5" }, ] From f08efa0ec73e4ab01915720625094e4d9006647e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 20:28:40 +0800 Subject: [PATCH 03/20] build(deps): bump rustls-webpki from 0.103.12 to 0.103.13 (#576) Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.12 to 0.103.13. - [Release notes](https://github.com/rustls/webpki/releases) - [Commits](https://github.com/rustls/webpki/compare/v/0.103.12...v/0.103.13) --- updated-dependencies: - dependency-name: rustls-webpki dependency-version: 0.103.13 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76d71afa..413720f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,9 +2748,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", From dca52ae0140ea337b49563206a69b1e820614ec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:17:40 +0800 Subject: [PATCH 04/20] build(deps): bump openssl from 0.10.76 to 0.10.78 (#574) Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.76 to 0.10.78. - [Release notes](https://github.com/rust-openssl/rust-openssl/releases) - [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.76...openssl-v0.10.78) --- updated-dependencies: - dependency-name: openssl dependency-version: 0.10.78 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 413720f0..45e6f675 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1766,7 +1766,7 @@ dependencies = [ "portable-atomic-util", "serde_core", "wasm-bindgen", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2141,9 +2141,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", @@ -2173,9 +2173,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index 57e4a097..debb92c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ crc-fast = "1.9.0" crc32c = "0.6.8" hmac = "0.13.0" md-5 = "0.11.0" -openssl = "0.10.76" +openssl = "0.10.78" sha1 = "0.11.0" sha2 = "0.11.0" subtle = "2.6.1" From 12028c873ed1aad406a232ef2ac096c882e545bf Mon Sep 17 00:00:00 2001 From: Nugine Date: Sun, 26 Apr 2026 23:59:35 +0800 Subject: [PATCH 05/20] feat(s3s-fs): support conditional copy in copy_object (#577) * feat(s3s-fs): support conditional copy in copy_object * fix(s3s-fs): address conditional copy review feedback * fix(s3s-fs): apply review feedback on copy_object precondition handling and ETag derivation Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/cc65f831-7a90-40b4-85d8-8b7d1a31a1cf Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --- crates/s3s-fs/src/s3.rs | 72 ++++++- crates/s3s-fs/tests/it_aws.rs | 357 ++++++++++++++++++++++++++++++++++ 2 files changed, 420 insertions(+), 9 deletions(-) diff --git a/crates/s3s-fs/src/s3.rs b/crates/s3s-fs/src/s3.rs index 67cdb41b..bcf1370d 100644 --- a/crates/s3s-fs/src/s3.rs +++ b/crates/s3s-fs/src/s3.rs @@ -91,34 +91,88 @@ impl S3 for FileSystem { return Err(s3_error!(NoSuchBucket)); } + let file_metadata = try_!(fs::metadata(&src_path).await); + let src_last_modified = Timestamp::from(try_!(file_metadata.modified())); + + // Always load internal info – needed for ETag derivation and checksum propagation. + let src_info = self.load_internal_info(bucket, key).await?; + + // Derive source ETag from stored internal info when available. + // For ETag-based conditions, fall back to MD5 only when no stored ETag exists. + let mut src_etag: Option = src_info.as_ref().and_then(crate::checksum::load_e_tag).map(ETag::Strong); + + // S3 precedence: If-Match overrides If-Unmodified-Since. + if let Some(ref condition) = input.copy_source_if_match { + if src_etag.is_none() { + src_etag = Some(ETag::Strong(self.get_md5_sum(bucket, key).await?)); + } + let src = src_etag.as_ref().ok_or_else(|| s3_error!(InternalError))?; + let matches = match condition { + ETagCondition::Any => true, + ETagCondition::ETag(etag) => src.strong_cmp(etag), + }; + if !matches { + return Err(s3_error!(PreconditionFailed)); + } + } else if let Some(ref if_unmodified_since) = input.copy_source_if_unmodified_since + && src_last_modified > *if_unmodified_since + { + return Err(s3_error!(PreconditionFailed)); + } + + // S3 precedence: If-None-Match overrides If-Modified-Since. + if let Some(ref condition) = input.copy_source_if_none_match { + if src_etag.is_none() { + src_etag = Some(ETag::Strong(self.get_md5_sum(bucket, key).await?)); + } + let src = src_etag.as_ref().ok_or_else(|| s3_error!(InternalError))?; + let matches = match condition { + ETagCondition::Any => true, + ETagCondition::ETag(etag) => src.weak_cmp(etag), + }; + if matches { + return Err(s3_error!(PreconditionFailed)); + } + } else if let Some(ref if_modified_since) = input.copy_source_if_modified_since + && src_last_modified <= *if_modified_since + { + return Err(s3_error!(PreconditionFailed)); + } + if let Some(dir_path) = dst_path.parent() { try_!(fs::create_dir_all(&dir_path).await); } - let file_metadata = try_!(fs::metadata(&src_path).await); - let last_modified = Timestamp::from(try_!(file_metadata.modified())); - let _ = try_!(fs::copy(&src_path, &dst_path).await); debug!(from = %src_path.display(), to = %dst_path.display(), "copy file"); + let dst_metadata = try_!(fs::metadata(&dst_path).await); + let dst_last_modified = Timestamp::from(try_!(dst_metadata.modified())); + + // Derive the destination ETag from the source ETag when available. + // This preserves non-MD5 ETag formats (e.g., multipart `{hash}-{part_count}`) + // and avoids re-hashing the destination file. + let dst_etag_str = match src_etag { + Some(etag) => etag.into_value(), + None => self.get_md5_sum(&input.bucket, &input.key).await?, + }; + let src_metadata_path = self.get_metadata_path(bucket, key, None)?; if src_metadata_path.exists() { let dst_metadata_path = self.get_metadata_path(&input.bucket, &input.key, None)?; let _ = try_!(fs::copy(src_metadata_path, dst_metadata_path).await); } - let md5_sum = self.get_md5_sum(bucket, key).await?; - { - let mut info = self.load_internal_info(bucket, key).await?.unwrap_or_default(); - crate::checksum::save_e_tag(&mut info, &md5_sum); + let mut info = src_info.unwrap_or_default(); + crate::checksum::save_e_tag(&mut info, &dst_etag_str); self.save_internal_info(&input.bucket, &input.key, &info).await?; } let copy_object_result = CopyObjectResult { - e_tag: Some(ETag::Strong(md5_sum)), - last_modified: Some(last_modified), + e_tag: Some(ETag::Strong(dst_etag_str)), + last_modified: Some(dst_last_modified), ..Default::default() }; diff --git a/crates/s3s-fs/tests/it_aws.rs b/crates/s3s-fs/tests/it_aws.rs index 24af7a9f..22b31be1 100644 --- a/crates/s3s-fs/tests/it_aws.rs +++ b/crates/s3s-fs/tests/it_aws.rs @@ -1610,6 +1610,363 @@ async fn test_copy_object_nested_dst() -> Result<()> { Ok(()) } +/// Test conditional copy with `x-amz-copy-source-if-match`. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_if_match() -> Result<()> { + use aws_sdk_s3::primitives::DateTime; + use aws_sdk_s3::primitives::DateTimeFormat; + + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-cond-copy-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + create_bucket(&c, bucket).await?; + + let src_key = "source.txt"; + let content = "conditional copy content"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(content.as_bytes())) + .send() + .await?; + + let get_result = c.get_object().bucket(bucket).key(src_key).send().await?; + let e_tag = get_result.e_tag().expect("get_object should return e_tag").to_owned(); + let _ = get_result.body.collect().await?; + + let copy_source = format!("{bucket}/{src_key}"); + + let dst_key = "dest-match.txt"; + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .copy_source_if_match(&e_tag) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + let dst_key2 = "dest-match-wildcard.txt"; + let past = DateTime::from_str("Thu, 01 Jan 2000 00:00:00 GMT", DateTimeFormat::HttpDate)?; + c.copy_object() + .bucket(bucket) + .key(dst_key2) + .copy_source(©_source) + .copy_source_if_match("*") + .copy_source_if_unmodified_since(past) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key2).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + let dst_key3 = "dest-nomatch.txt"; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key3) + .copy_source(©_source) + .copy_source_if_match("\"nonexistent-etag\"") + .send() + .await + .expect_err("Expected copy with non-matching If-Match to fail"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_object(&c, bucket, dst_key2).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// Test conditional copy with `x-amz-copy-source-if-none-match`. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_if_none_match() -> Result<()> { + use aws_sdk_s3::primitives::DateTime; + use aws_sdk_s3::primitives::DateTimeFormat; + + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-cond-copy-nm-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + create_bucket(&c, bucket).await?; + + let src_key = "source.txt"; + let content = "conditional copy none match"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(content.as_bytes())) + .send() + .await?; + + let get_result = c.get_object().bucket(bucket).key(src_key).send().await?; + let e_tag = get_result.e_tag().expect("get_object should return e_tag").to_owned(); + let _ = get_result.body.collect().await?; + + let copy_source = format!("{bucket}/{src_key}"); + + let dst_key = "dest-none-match-ok.txt"; + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .copy_source_if_none_match("\"different-etag\"") + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + let dst_key2 = "dest-none-match-fail.txt"; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key2) + .copy_source(©_source) + .copy_source_if_none_match(&e_tag) + .send() + .await + .expect_err("Expected copy with matching If-None-Match to fail"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + let dst_key3 = "dest-none-match-wildcard.txt"; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key3) + .copy_source(©_source) + .copy_source_if_none_match("*") + .send() + .await + .expect_err("Expected copy with wildcard If-None-Match to fail for existing source"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + let dst_key4 = "dest-none-match-precedence.txt"; + let future = DateTime::from_str("Thu, 01 Jan 2099 00:00:00 GMT", DateTimeFormat::HttpDate)?; + c.copy_object() + .bucket(bucket) + .key(dst_key4) + .copy_source(©_source) + .copy_source_if_none_match("\"different-etag\"") + .copy_source_if_modified_since(future) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key4).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_object(&c, bucket, dst_key4).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// Test conditional copy with `x-amz-copy-source-if-modified-since` and +/// `x-amz-copy-source-if-unmodified-since`. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_if_modified_since() -> Result<()> { + use aws_sdk_s3::primitives::DateTime; + use aws_sdk_s3::primitives::DateTimeFormat; + + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-cond-copy-ts-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + create_bucket(&c, bucket).await?; + + let src_key = "source.txt"; + let content = "conditional copy timestamp"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(content.as_bytes())) + .send() + .await?; + + let copy_source = format!("{bucket}/{src_key}"); + + let dst_key = "dest-modified-ok.txt"; + let past = DateTime::from_str("Thu, 01 Jan 2000 00:00:00 GMT", DateTimeFormat::HttpDate)?; + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .copy_source_if_modified_since(past) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + let dst_key2 = "dest-modified-fail.txt"; + let future = DateTime::from_str("Thu, 01 Jan 2099 00:00:00 GMT", DateTimeFormat::HttpDate)?; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key2) + .copy_source(©_source) + .copy_source_if_modified_since(future) + .send() + .await + .expect_err("Expected copy with future if-modified-since to fail"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + let dst_key3 = "dest-unmodified-ok.txt"; + let future = DateTime::from_str("Thu, 01 Jan 2099 00:00:00 GMT", DateTimeFormat::HttpDate)?; + c.copy_object() + .bucket(bucket) + .key(dst_key3) + .copy_source(©_source) + .copy_source_if_unmodified_since(future) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key3).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + let dst_key4 = "dest-unmodified-fail.txt"; + let past = DateTime::from_str("Thu, 01 Jan 2000 00:00:00 GMT", DateTimeFormat::HttpDate)?; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key4) + .copy_source(©_source) + .copy_source_if_unmodified_since(past) + .send() + .await + .expect_err("Expected copy with past if-unmodified-since to fail"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_object(&c, bucket, dst_key3).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// Test conditional copy against a multipart source object's persisted `ETag`. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_conditional_with_multipart_source_etag() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-cond-copy-multipart-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + create_bucket(&c, bucket).await?; + + let src_key = "source-multipart.txt"; + let content = "multipart conditional copy content"; + + let upload_id = c + .create_multipart_upload() + .bucket(bucket) + .key(src_key) + .send() + .await? + .upload_id + .expect("create_multipart_upload should return upload_id"); + + let upload_result = c + .upload_part() + .bucket(bucket) + .key(src_key) + .upload_id(&upload_id) + .body(ByteStream::from_static(content.as_bytes())) + .part_number(1) + .send() + .await?; + + let upload = CompletedMultipartUpload::builder() + .set_parts(Some(vec![ + CompletedPart::builder() + .e_tag(upload_result.e_tag.expect("upload_part should return e_tag")) + .part_number(1) + .build(), + ])) + .build(); + + let complete_result = c + .complete_multipart_upload() + .bucket(bucket) + .key(src_key) + .multipart_upload(upload) + .upload_id(&upload_id) + .send() + .await?; + let multipart_e_tag = complete_result + .e_tag() + .expect("complete_multipart_upload should return e_tag") + .to_owned(); + + let copy_source = format!("{bucket}/{src_key}"); + + let dst_key = "dest-multipart-match.txt"; + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .copy_source_if_match(&multipart_e_tag) + .send() + .await?; + + let ans = c.get_object().bucket(bucket).key(dst_key).send().await?; + let body = ans.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes()); + + // The destination ETag should match the source multipart ETag (format preserved during copy). + let head = c.head_object().bucket(bucket).key(dst_key).send().await?; + let dst_etag = head.e_tag().expect("head_object should return e_tag"); + assert_eq!( + dst_etag, multipart_e_tag, + "destination ETag should match source multipart ETag after copy" + ); + + let dst_key2 = "dest-multipart-none-match.txt"; + let err = c + .copy_object() + .bucket(bucket) + .key(dst_key2) + .copy_source(©_source) + .copy_source_if_none_match(&multipart_e_tag) + .send() + .await + .expect_err("Expected matching multipart ETag to fail If-None-Match"); + let service_err = err.into_service_error(); + assert_eq!(service_err.code(), Some("PreconditionFailed")); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + /// Regression test for /// /// `list_objects_v2` prefix matching should use string-based matching (not `Path::starts_with`) From 7f861fdcec903197ec1eb825c69011471fc4379b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 07:43:43 +0000 Subject: [PATCH 06/20] ci(e2e-s3tests): Pin s3-tests revision for deterministic CI (#581) * ci: pin s3-tests e2e revision Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/6d3a763a-ed67-4a3c-b40f-436c8e523371 Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * ci: centralize s3-tests revision Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/229e0023-9e60-4e6d-87e9-babbe81025ea Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * ci: trim centralized s3-tests ref Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/229e0023-9e60-4e6d-87e9-babbe81025ea Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * ci: document centralized s3-tests ref Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/229e0023-9e60-4e6d-87e9-babbe81025ea Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> * ci: share s3-tests ref loader Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/229e0023-9e60-4e6d-87e9-babbe81025ea Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --- .github/workflows/ci.yml | 7 ++++++- scripts/e2e-s3tests.sh | 18 ++++++++---------- scripts/s3tests.env | 3 +++ scripts/source-s3tests-ref.sh | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 scripts/s3tests.env create mode 100644 scripts/source-s3tests-ref.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d7df233..c720117d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,7 +194,12 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/setup - name: Clone s3-tests - run: git clone --depth 1 https://github.com/ceph/s3-tests.git /tmp/s3-tests + run: | + . scripts/source-s3tests-ref.sh + git init /tmp/s3-tests + git -C /tmp/s3-tests remote add origin https://github.com/ceph/s3-tests.git + git -C /tmp/s3-tests fetch --depth 1 origin "$S3TESTS_REF" + git -C /tmp/s3-tests reset --hard FETCH_HEAD - name: Get s3-tests requirements hash id: s3tests-hash run: echo "hash=$(sha256sum /tmp/s3-tests/requirements.txt | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" diff --git a/scripts/e2e-s3tests.sh b/scripts/e2e-s3tests.sh index 88bb9bdf..37e8c489 100755 --- a/scripts/e2e-s3tests.sh +++ b/scripts/e2e-s3tests.sh @@ -8,6 +8,8 @@ REPORT_DIR="/tmp/s3s-s3tests-report" MINIO_DIR="/tmp/s3s-s3tests-minio" S3S_PROXY_PID="" +. "$ROOT_DIR/scripts/source-s3tests-ref.sh" + mkdir -p "$TARGET_DIR" mkdir -p "$REPORT_DIR" mkdir -p "$MINIO_DIR" @@ -122,18 +124,14 @@ ensure_proxy_running ensure_minio_running "$MINIO_CONTAINER_ID" if [ -d "$S3TESTS_DIR/.git" ]; then - default_branch=$(git -C "$S3TESTS_DIR" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||') - if [ -z "$default_branch" ]; then - default_branch=$(git -C "$S3TESTS_DIR" remote show origin | awk '/HEAD branch/ {print $NF}') - fi - if [ -z "$default_branch" ]; then - default_branch="master" - fi - git -C "$S3TESTS_DIR" fetch --depth 1 origin "$default_branch" - git -C "$S3TESTS_DIR" reset --hard "origin/$default_branch" + git -C "$S3TESTS_DIR" fetch --depth 1 origin "$S3TESTS_REF" + git -C "$S3TESTS_DIR" reset --hard FETCH_HEAD else rm -rf "$S3TESTS_DIR" - git clone --depth 1 https://github.com/ceph/s3-tests.git "$S3TESTS_DIR" + git init "$S3TESTS_DIR" + git -C "$S3TESTS_DIR" remote add origin https://github.com/ceph/s3-tests.git + git -C "$S3TESTS_DIR" fetch --depth 1 origin "$S3TESTS_REF" + git -C "$S3TESTS_DIR" reset --hard FETCH_HEAD fi if command -v sha256sum >/dev/null 2>&1; then REQUIREMENTS_HASH=$(sha256sum "$S3TESTS_DIR/requirements.txt" | cut -d' ' -f1) diff --git a/scripts/s3tests.env b/scripts/s3tests.env new file mode 100644 index 00000000..2db0af4c --- /dev/null +++ b/scripts/s3tests.env @@ -0,0 +1,3 @@ +# Pinned ceph/s3-tests revision used by e2e CI. Update this when refreshing +# the s3-tests compatibility baseline. +S3TESTS_REF=fb8b73092bb1dd8db829f1205a9e52e73bf9a232 diff --git a/scripts/source-s3tests-ref.sh b/scripts/source-s3tests-ref.sh new file mode 100644 index 00000000..e8da6b40 --- /dev/null +++ b/scripts/source-s3tests-ref.sh @@ -0,0 +1,14 @@ +S3TESTS_REF_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +S3TESTS_REF_FILE="${S3TESTS_REF_FILE:-$S3TESTS_REF_SCRIPT_DIR/s3tests.env}" + +if [ -z "${S3TESTS_REF:-}" ]; then + if [ ! -r "$S3TESTS_REF_FILE" ]; then + echo "s3-tests ref file not readable: $S3TESTS_REF_FILE" >&2 + return 1 2>/dev/null || exit 1 + fi + . "$S3TESTS_REF_FILE" +fi +if [ -z "${S3TESTS_REF:-}" ]; then + echo "s3-tests ref is empty" >&2 + return 1 2>/dev/null || exit 1 +fi From 82f60ccf6bccb84335329d14f939219ff6dfeb0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 07:46:48 +0000 Subject: [PATCH 07/20] build(deps): bump the dependencies group with 10 updates (#578) Bumps the dependencies group with 10 updates: | Package | From | To | | --- | --- | --- | | [bytestring](https://github.com/actix/actix-net) | `1.5.0` | `1.5.1` | | [indexmap](https://github.com/indexmap-rs/indexmap) | `2.13.1` | `2.14.0` | | [uuid](https://github.com/uuid-rs/uuid) | `1.23.0` | `1.23.1` | | [tokio](https://github.com/tokio-rs/tokio) | `1.51.0` | `1.52.1` | | [axum](https://github.com/tokio-rs/axum) | `0.8.8` | `0.8.9` | | [reqwest](https://github.com/seanmonstar/reqwest) | `0.13.2` | `0.13.3` | | [clap](https://github.com/clap-rs/clap) | `4.6.0` | `4.6.1` | | [aws-sdk-s3](https://github.com/awslabs/aws-sdk-rust) | `1.128.0` | `1.129.0` | | [aws-sdk-sts](https://github.com/awslabs/aws-sdk-rust) | `1.101.0` | `1.102.0` | | [aws-smithy-runtime-api](https://github.com/smithy-lang/smithy-rs) | `1.11.6` | `1.12.0` | Updates `bytestring` from 1.5.0 to 1.5.1 - [Release notes](https://github.com/actix/actix-net/releases) - [Commits](https://github.com/actix/actix-net/compare/bytestring-v1.5.0...bytestring-v1.5.1) Updates `indexmap` from 2.13.1 to 2.14.0 - [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.13.1...2.14.0) Updates `uuid` from 1.23.0 to 1.23.1 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.0...v1.23.1) Updates `tokio` from 1.51.0 to 1.52.1 - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.51.0...tokio-1.52.1) Updates `axum` from 0.8.8 to 0.8.9 - [Release notes](https://github.com/tokio-rs/axum/releases) - [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.8.8...axum-v0.8.9) Updates `reqwest` from 0.13.2 to 0.13.3 - [Release notes](https://github.com/seanmonstar/reqwest/releases) - [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md) - [Commits](https://github.com/seanmonstar/reqwest/compare/v0.13.2...v0.13.3) Updates `clap` from 4.6.0 to 4.6.1 - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.0...clap_complete-v4.6.1) Updates `aws-sdk-s3` from 1.128.0 to 1.129.0 - [Release notes](https://github.com/awslabs/aws-sdk-rust/releases) - [Commits](https://github.com/awslabs/aws-sdk-rust/commits) Updates `aws-sdk-sts` from 1.101.0 to 1.102.0 - [Release notes](https://github.com/awslabs/aws-sdk-rust/releases) - [Commits](https://github.com/awslabs/aws-sdk-rust/commits) Updates `aws-smithy-runtime-api` from 1.11.6 to 1.12.0 - [Release notes](https://github.com/smithy-lang/smithy-rs/releases) - [Changelog](https://github.com/smithy-lang/smithy-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/smithy-lang/smithy-rs/commits) --- updated-dependencies: - dependency-name: bytestring dependency-version: 1.5.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: indexmap dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: uuid dependency-version: 1.23.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: tokio dependency-version: 1.52.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: axum dependency-version: 0.8.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: reqwest dependency-version: 0.13.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: clap dependency-version: 4.6.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: aws-sdk-s3 dependency-version: 1.129.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: aws-sdk-sts dependency-version: 1.102.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: aws-smithy-runtime-api dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 64 ++++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 20 ++++++++--------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45e6f675..e4804f31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.128.0" +version = "1.129.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99304b64672e0d81a3c100a589b93d9ef5e9c0ce12e21c848fd39e50f493c2a1" +checksum = "6d4e8410fadbc0ee453145dd77a4958227b18b05bf67c2795d0a8b8596c9aa0f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.101.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a" +checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" dependencies = [ "aws-credential-types", "aws-runtime", @@ -463,11 +463,12 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.11.6" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ab3c9c29791ba4ba02b780a3049e21ec63dabda09268b175272c3733a79e6" +checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" dependencies = [ "aws-smithy-async", + "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", @@ -478,6 +479,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "aws-smithy-types" version = "1.4.7" @@ -539,9 +551,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -680,9 +692,9 @@ dependencies = [ [[package]] name = "bytestring" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ "bytes", ] @@ -736,9 +748,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -758,9 +770,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -1402,6 +1414,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" @@ -1714,12 +1732,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2588,9 +2606,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", @@ -3422,9 +3440,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -3661,9 +3679,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index debb92c3..c885e483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,11 @@ debug = "line-tables-only" arc-swap = "1.9.1" arrayvec = "0.7.6" bytes = "1.11.1" -bytestring = "1.5.0" -indexmap = "2.13.1" +bytestring = "1.5.1" +indexmap = "2.14.0" once_cell = "1.21.4" smallvec = "1.15.1" -uuid = "1.23.0" +uuid = "1.23.1" # Text encoding atoi = { version = "2.0.0", default-features = false } @@ -83,12 +83,12 @@ futures = { version = "0.3.32", default-features = false } futures-util = "0.3.32" pin-project-lite = "0.2.17" sync_wrapper = { version = "1.0.2", default-features = false } -tokio = "1.51.0" +tokio = "1.52.1" tokio-util = "0.7.18" transform-stream = "0.3.1" # HTTP -axum = "0.8.8" +axum = "0.8.9" http = "1.4.0" http-body = "1.0.1" http-body-util = "0.1.3" @@ -100,7 +100,7 @@ tokio-rustls = "0.26.4" tower = { version = "0.5.3", default-features = false } url = "2.5.8" urlencoding = "2.1.3" -reqwest = { version = "0.13.2", default-features = false, features = ["rustls", "form", "query"] } +reqwest = { version = "0.13.3", default-features = false, features = ["rustls", "form", "query"] } # Logging & observability tracing = "0.1.44" @@ -108,7 +108,7 @@ tracing-error = "0.2.1" tracing-subscriber = { version = "0.3.23", features = ["env-filter", "time"] } # CLI -clap = { version = "4.6.0", features = ["derive"] } +clap = { version = "4.6.1", features = ["derive"] } # Storage backends opendal = "0.55.0" @@ -116,8 +116,8 @@ opendal = "0.55.0" # AWS SDK aws-config = { version = "1.8.15", default-features = false } aws-credential-types = "1.2.14" -aws-sdk-s3 = { version = "1.128.0", default-features = false, features = ["default-https-client", "http-1x", "rt-tokio", "sigv4a"] } -aws-sdk-sts = { version = "1.101.0", default-features = false, features = ["default-https-client", "rt-tokio"] } -aws-smithy-runtime-api = "1.11.6" +aws-sdk-s3 = { version = "1.129.0", default-features = false, features = ["default-https-client", "http-1x", "rt-tokio", "sigv4a"] } +aws-sdk-sts = { version = "1.102.0", default-features = false, features = ["default-https-client", "rt-tokio"] } +aws-smithy-runtime-api = "1.12.0" aws-smithy-types = "1.4.7" aws-smithy-types-convert = "0.60.14" From 4095af3cd67d7a62593203fa3eabf7cf8e1d8a9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 15:46:59 +0800 Subject: [PATCH 08/20] build(deps): bump actions/upload-pages-artifact (#579) Bumps the dependencies group with 1 update: [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact). Updates `actions/upload-pages-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 27ba4dfa..45e18ac6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: - name: Add .nojekyll file run: touch target/doc/.nojekyll - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: ./target/doc From 1fa26d76b97907707443b832e5adbf515b970571 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 07:55:43 +0000 Subject: [PATCH 09/20] build(s3s-aws): make `s3s-aws` opt out of `aws-sdk-s3` default features (#580) * Initial plan * fix: make s3s-aws aws-sdk-s3 features explicit Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/6b9c669a-1e52-47cd-9ed9-b0587726def3 Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --- crates/s3s-aws/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/s3s-aws/Cargo.toml b/crates/s3s-aws/Cargo.toml index ac384d1d..98c38537 100644 --- a/crates/s3s-aws/Cargo.toml +++ b/crates/s3s-aws/Cargo.toml @@ -18,7 +18,7 @@ minio = ["s3s/minio"] [dependencies] async-trait.workspace = true -aws-sdk-s3.workspace = true +aws-sdk-s3 = { workspace = true, default-features = false, features = ["default-https-client", "http-1x", "rt-tokio", "sigv4a"] } aws-smithy-runtime-api = { workspace = true, features = ["client", "http-1x"] } aws-smithy-types = { workspace = true, features = ["http-body-1-x"] } aws-smithy-types-convert = { workspace = true, features = ["convert-time"] } From 62cb4a71dd759a6ec56b64c4c42fcc183a2c6a52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 18:56:34 +0800 Subject: [PATCH 10/20] build(deps): bump openssl from 0.10.78 to 0.10.79 (#582) Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.78 to 0.10.79. - [Release notes](https://github.com/rust-openssl/rust-openssl/releases) - [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.78...openssl-v0.10.79) --- updated-dependencies: - dependency-name: openssl dependency-version: 0.10.79 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 9 ++++----- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4804f31..192023b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2159,15 +2159,14 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.78" +version = "0.10.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -2191,9 +2190,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.114" +version = "0.9.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index c885e483..18faa4fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ crc-fast = "1.9.0" crc32c = "0.6.8" hmac = "0.13.0" md-5 = "0.11.0" -openssl = "0.10.78" +openssl = "0.10.79" sha1 = "0.11.0" sha2 = "0.11.0" subtle = "2.6.1" From 1b3408cea16564a694b294851f7f91ffb5c4a9b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 22:58:51 +0800 Subject: [PATCH 11/20] build(deps): bump urllib3 from 2.6.3 to 2.7.0 (#587) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 9a5bd0a3..20b45880 100644 --- a/uv.lock +++ b/uv.lock @@ -333,9 +333,9 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] From 928550ad2b55135c8ab0c6ba3e418b3656321c2a Mon Sep 17 00:00:00 2001 From: Nugine Date: Mon, 18 May 2026 00:14:51 +0800 Subject: [PATCH 12/20] fix(s3s): allow raw URI path SigV4 fallback (#589) * fix: allow raw URI path SigV4 fallback * test: cover presigned raw URI path fallback * fix: satisfy clippy for raw path signature fallback * fix: improve raw URI path signature verification and add tests for fallback scenarios --------- Co-authored-by: overtrue Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/s3s/src/ops/mod.rs | 3 +- crates/s3s/src/ops/signature.rs | 749 ++++++++++++++++++++++++++----- crates/s3s/src/ops/tests.rs | 3 +- crates/s3s/src/sig_v4/methods.rs | 62 ++- 4 files changed, 684 insertions(+), 133 deletions(-) diff --git a/crates/s3s/src/ops/mod.rs b/crates/s3s/src/ops/mod.rs index d002cbca..43488d26 100644 --- a/crates/s3s/src/ops/mod.rs +++ b/crates/s3s/src/ops/mod.rs @@ -364,7 +364,8 @@ async fn prepare(req: &mut Request, ccx: &CallContext<'_>) -> S3Result qs: req.s3ext.qs.as_ref(), hs, - decoded_uri_path, + decoded_uri_path: &decoded_uri_path, + raw_uri_path: req.uri.path(), vh_bucket, content_length, diff --git a/crates/s3s/src/ops/signature.rs b/crates/s3s/src/ops/signature.rs index 76f96330..bf9105e6 100644 --- a/crates/s3s/src/ops/signature.rs +++ b/crates/s3s/src/ops/signature.rs @@ -71,7 +71,8 @@ pub struct SignatureContext<'a> { pub qs: Option<&'a OrderedQs>, pub hs: OrderedHeaders<'a>, - pub decoded_uri_path: String, + pub decoded_uri_path: &'a str, + pub raw_uri_path: &'a str, pub vh_bucket: Option<&'a str>, pub content_length: Option, @@ -96,6 +97,57 @@ fn require_auth(auth: Option<&dyn S3Auth>) -> S3Result<&dyn S3Auth> { auth.ok_or_else(|| s3_error!(NotImplemented, "This service has no authentication provider")) } +fn has_unencoded_reserved_path_char(path: &str) -> bool { + // Percent-encoded paths should be handled by normal S3 canonicalization. + path.bytes().any(|b| { + !matches!( + b, + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' | b'/' | b'%' + ) + }) +} + +struct SignatureVerificationContext<'a> { + expected_signature: &'a str, + raw_uri_path: &'a str, + secret_key: &'a SecretKey, + amz_date: &'a AmzDate, + region: &'a str, + service: &'a str, +} + +impl SignatureVerificationContext<'_> { + fn verify_with_raw_path_fallback( + &self, + canonical_request: &str, + raw_canonical_request: impl FnOnce() -> String, + ) -> S3Result { + let string_to_sign = sig_v4::create_string_to_sign(canonical_request, self.amz_date, self.region, self.service); + let signature = sig_v4::calculate_signature(&string_to_sign, self.secret_key, self.amz_date, self.region, self.service); + + if signature == self.expected_signature { + return Ok(signature); + } + + if !has_unencoded_reserved_path_char(self.raw_uri_path) { + debug!(?signature, expected=?self.expected_signature, "signature mismatch"); + return Err(s3_error!(SignatureDoesNotMatch)); + } + + let canonical_request = raw_canonical_request(); + let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, self.amz_date, self.region, self.service); + let raw_signature = + sig_v4::calculate_signature(&string_to_sign, self.secret_key, self.amz_date, self.region, self.service); + + if raw_signature != self.expected_signature { + debug!(?signature, ?raw_signature, expected=?self.expected_signature, "signature mismatch"); + return Err(s3_error!(SignatureDoesNotMatch)); + } + + Ok(raw_signature) + } +} + impl SignatureContext<'_> { pub async fn check(&mut self) -> S3Result> { if self.req_method == Method::POST @@ -289,36 +341,34 @@ impl SignatureContext<'_> { )); } - let signature = { - let headers = self.hs.find_multiple_with_on_missing(&presigned_url.signed_headers, |name| { - // HTTP/2 replaces `host` header with `:authority` - // but `:authority` is not in the request headers - // so we need to add it back if `host` is in the signed headers - if name == "host" - && matches!(self.req_version, ::http::Version::HTTP_2 | ::http::Version::HTTP_3) - && let Some(authority) = self.req_uri.authority() - { - return Some(authority.as_str()); - } - None - }); - - let method = &self.req_method; - let uri_path = &self.decoded_uri_path; - - let canonical_request = sig_v4::create_presigned_canonical_request(method, uri_path, qs.as_ref(), &headers); - - let amz_date = &presigned_url.amz_date; - let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, amz_date, region, service); + let expected_signature = presigned_url.signature; + let headers = self.hs.find_multiple_with_on_missing(&presigned_url.signed_headers, |name| { + // HTTP/2 replaces `host` header with `:authority` + // but `:authority` is not in the request headers + // so we need to add it back if `host` is in the signed headers + if name == "host" + && matches!(self.req_version, ::http::Version::HTTP_2 | ::http::Version::HTTP_3) + && let Some(authority) = self.req_uri.authority() + { + return Some(authority.as_str()); + } + None + }); - sig_v4::calculate_signature(&string_to_sign, &secret_key, amz_date, region, service) + let method = &self.req_method; + let amz_date = &presigned_url.amz_date; + let verifier = SignatureVerificationContext { + expected_signature, + raw_uri_path: self.raw_uri_path, + secret_key: &secret_key, + amz_date, + region, + service, }; - - let expected_signature = presigned_url.signature; - if signature != expected_signature { - debug!(?signature, expected=?expected_signature, "signature mismatch"); - return Err(s3_error!(SignatureDoesNotMatch)); - } + let canonical_request = sig_v4::create_presigned_canonical_request(method, self.decoded_uri_path, qs.as_ref(), &headers); + verifier.verify_with_raw_path_fallback(&canonical_request, || { + sig_v4::create_presigned_canonical_request_with_raw_uri_path(method, self.raw_uri_path, qs.as_ref(), &headers) + })?; Ok(CredentialsExt { access_key: access_key.into(), @@ -363,101 +413,74 @@ impl SignatureContext<'_> { let is_stream = amz_content_sha256.is_some_and(|v| v.is_streaming()); - let signature = { - let method = &self.req_method; - let uri_path = &self.decoded_uri_path; - let query_strings: &[(String, String)] = self.qs.as_ref().map_or(&[], AsRef::as_ref); - - // FIXME: throw error if any signed header is not in the request - // `host` header need to be special handled - - // here requires that `auth.signed_headers` is sorted - let headers = self.hs.find_multiple_with_on_missing(&authorization.signed_headers, |name| { - // HTTP/2 replaces `host` header with `:authority` - // but `:authority` is not in the request headers - // so we need to add it back if `host` is in the signed headers - if name == "host" - && self.req_version == ::http::Version::HTTP_2 - && let Some(authority) = self.req_uri.authority() - { - return Some(authority.as_str()); - } - None - }); - - let canonical_request = match amz_content_sha256 { - Some(AmzContentSha256::StreamingAws4HmacSha256Payload) => { - sig_v4::create_canonical_request(method, uri_path, query_strings, &headers, sig_v4::Payload::MultipleChunks) - } - Some(AmzContentSha256::StreamingAws4HmacSha256PayloadTrailer) => sig_v4::create_canonical_request( - method, - uri_path, - query_strings, - &headers, - sig_v4::Payload::MultipleChunksWithTrailer, - ), - Some(AmzContentSha256::UnsignedPayload) => { - sig_v4::create_canonical_request(method, uri_path, query_strings, &headers, sig_v4::Payload::Unsigned) - } - Some(AmzContentSha256::StreamingUnsignedPayloadTrailer) => sig_v4::create_canonical_request( - method, - uri_path, - query_strings, - &headers, - sig_v4::Payload::UnsignedMultipleChunksWithTrailer, - ), - Some(AmzContentSha256::SingleChunk(payload_checksum)) => sig_v4::create_canonical_request( - method, - uri_path, - query_strings, - &headers, - sig_v4::Payload::SingleChunk(payload_checksum), - ), - Some( - AmzContentSha256::StreamingAws4EcdsaP256Sha256Payload - | AmzContentSha256::StreamingAws4EcdsaP256Sha256PayloadTrailer, - ) => { - return Err(s3_error!(NotImplemented, "AWS4-ECDSA-P256-SHA256 signing method is not implemented yet")); - } - None => { - // For STS requests, x-amz-content-sha256 header is not required - // For S3 requests, this case should have been caught earlier (see lines 325-327) - if service == "sts" { - // STS requests require computing the payload hash from the body - // Read the body (it's small for STS requests like AssumeRole) - let body_bytes = self - .req_body - .store_all_limited(MAX_STS_BODY_SIZE) - .await - .map_err(|e| invalid_request!("failed to read STS request body: {}", e))?; - - // Compute SHA256 hash and convert to hex - let hash = hex_sha256(&body_bytes, str::to_owned); - - // Create canonical request with the computed hash - sig_v4::create_canonical_request( - method, - uri_path, - query_strings, - &headers, - sig_v4::Payload::SingleChunk(&hash), - ) - } else { - // According to AWS S3 protocol, x-amz-content-sha256 header is required for - // all S3 requests authenticated with Signature V4. Reject if missing. - return Err(invalid_request!("missing header: x-amz-content-sha256")); - } + let expected_signature = authorization.signature; + let method = &self.req_method; + let query_strings: &[(String, String)] = self.qs.as_ref().map_or(&[], AsRef::as_ref); + + // FIXME: throw error if any signed header is not in the request + // `host` header need to be special handled + + // here requires that `auth.signed_headers` is sorted + let headers = self.hs.find_multiple_with_on_missing(&authorization.signed_headers, |name| { + // HTTP/2 replaces `host` header with `:authority` + // but `:authority` is not in the request headers + // so we need to add it back if `host` is in the signed headers + if name == "host" + && self.req_version == ::http::Version::HTTP_2 + && let Some(authority) = self.req_uri.authority() + { + return Some(authority.as_str()); + } + None + }); + + let sts_payload_hash; + let payload = match amz_content_sha256 { + Some(AmzContentSha256::StreamingAws4HmacSha256Payload) => sig_v4::Payload::MultipleChunks, + Some(AmzContentSha256::StreamingAws4HmacSha256PayloadTrailer) => sig_v4::Payload::MultipleChunksWithTrailer, + Some(AmzContentSha256::UnsignedPayload) => sig_v4::Payload::Unsigned, + Some(AmzContentSha256::StreamingUnsignedPayloadTrailer) => sig_v4::Payload::UnsignedMultipleChunksWithTrailer, + Some(AmzContentSha256::SingleChunk(payload_checksum)) => sig_v4::Payload::SingleChunk(payload_checksum), + Some( + AmzContentSha256::StreamingAws4EcdsaP256Sha256Payload + | AmzContentSha256::StreamingAws4EcdsaP256Sha256PayloadTrailer, + ) => { + return Err(s3_error!(NotImplemented, "AWS4-ECDSA-P256-SHA256 signing method is not implemented yet")); + } + None => { + // For STS requests, x-amz-content-sha256 header is not required + // For S3 requests, this case should have been caught earlier. + if service == "sts" { + // STS requests require computing the payload hash from the body + // Read the body (it's small for STS requests like AssumeRole) + let body_bytes = self + .req_body + .store_all_limited(MAX_STS_BODY_SIZE) + .await + .map_err(|e| invalid_request!("failed to read STS request body: {}", e))?; + + sts_payload_hash = hex_sha256(&body_bytes, str::to_owned); + sig_v4::Payload::SingleChunk(&sts_payload_hash) + } else { + // According to AWS S3 protocol, x-amz-content-sha256 header is required for + // all S3 requests authenticated with Signature V4. Reject if missing. + return Err(invalid_request!("missing header: x-amz-content-sha256")); } - }; - let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, region, service); - sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, region, service) + } }; - let expected_signature = authorization.signature; - if signature != expected_signature { - debug!(?signature, expected=?expected_signature, "signature mismatch"); - return Err(s3_error!(SignatureDoesNotMatch)); - } + let verifier = SignatureVerificationContext { + expected_signature, + raw_uri_path: self.raw_uri_path, + secret_key: &secret_key, + amz_date: &amz_date, + region, + service, + }; + let canonical_request = sig_v4::create_canonical_request(method, self.decoded_uri_path, query_strings, &headers, payload); + let signature = verifier.verify_with_raw_path_fallback(&canonical_request, || { + sig_v4::create_canonical_request_with_raw_uri_path(method, self.raw_uri_path, query_strings, &headers, payload) + })?; if is_stream { // For streaming with trailers, AWS requires x-amz-trailer header present. @@ -686,6 +709,66 @@ mod tests { assert!(err.message().unwrap().contains("x-amz-content-sha256")); } + #[test] + fn raw_path_fallback_rejects_missing_or_mismatched_signatures() { + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let method = Method::GET; + let headers = OrderedHeaders::from_slice_unchecked(&[ + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "UNSIGNED-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ]); + + let canonical_request = sig_v4::create_canonical_request( + &method, + "/test-bucket/path", + &[] as &[(&str, &str)], + &headers, + sig_v4::Payload::Unsigned, + ); + let verifier = SignatureVerificationContext { + expected_signature: "0000000000000000000000000000000000000000000000000000000000000000", + raw_uri_path: "/test-bucket/path", + secret_key: &secret_key, + amz_date: &amz_date, + region: "us-east-1", + service: "s3", + }; + let err = verifier + .verify_with_raw_path_fallback(&canonical_request, || panic!("raw fallback should not be attempted")) + .expect_err("signature mismatch without raw reserved characters should be rejected"); + assert_eq!(err.code(), &S3ErrorCode::SignatureDoesNotMatch); + + let canonical_request = sig_v4::create_canonical_request( + &method, + "/test-bucket/path=", + &[] as &[(&str, &str)], + &headers, + sig_v4::Payload::Unsigned, + ); + let verifier = SignatureVerificationContext { + expected_signature: "0000000000000000000000000000000000000000000000000000000000000000", + raw_uri_path: "/test-bucket/path=", + secret_key: &secret_key, + amz_date: &amz_date, + region: "us-east-1", + service: "s3", + }; + let err = verifier + .verify_with_raw_path_fallback(&canonical_request, || { + sig_v4::create_canonical_request_with_raw_uri_path( + &method, + "/test-bucket/path=", + &[] as &[(&str, &str)], + &headers, + sig_v4::Payload::Unsigned, + ) + }) + .expect_err("raw fallback signature mismatch should be rejected"); + assert_eq!(err.code(), &S3ErrorCode::SignatureDoesNotMatch); + } + #[tokio::test] async fn post_signature_allows_anonymous() { use crate::config::{S3ConfigProvider, StaticConfigProvider}; @@ -718,7 +801,8 @@ file content\r\n\ req_body: &mut body, qs: None, hs: OrderedHeaders::from_slice_unchecked(&[]), - decoded_uri_path: "/test-bucket".to_owned(), + decoded_uri_path: "/test-bucket", + raw_uri_path: "/test-bucket", vh_bucket: None, content_length: None, mime: Some(mime), @@ -839,7 +923,8 @@ file content\r\n\ req_body: &mut body, qs: Some(&qs), hs: OrderedHeaders::from_slice_unchecked(&[]), - decoded_uri_path: "/test.txt".to_owned(), + decoded_uri_path: "/test.txt", + raw_uri_path: "/test.txt", vh_bucket: None, content_length: None, mime: None, @@ -856,6 +941,421 @@ file content\r\n\ assert_eq!(err.code(), &S3ErrorCode::NotImplemented); } + #[tokio::test] + async fn v4_presigned_url_accepts_standard_and_raw_uri_path_signatures() { + use crate::auth::SecretKey; + use crate::auth::SimpleAuth; + use crate::config::{S3ConfigProvider, StaticConfigProvider}; + use std::sync::Arc; + + let access_key = "AKIAIOSFODNN7EXAMPLE"; + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let auth = SimpleAuth::from_single(access_key, secret_key.clone()); + let config: Arc = Arc::new(StaticConfigProvider::default()); + + let method = Method::GET; + let uri = Uri::from_static("https://s3.amazonaws.com/test-bucket/path/sitemap.xmlage="); + let decoded_uri_path = "/test-bucket/path/sitemap.xmlage="; + let raw_uri_path = "/test-bucket/path/sitemap.xmlage="; + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let headers_for_signing = OrderedHeaders::from_slice_unchecked(&[("host", "s3.amazonaws.com")]); + let query_strings_for_signing = &[ + ("X-Amz-Algorithm", "AWS4-HMAC-SHA256"), + ("X-Amz-Credential", "AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request"), + ("X-Amz-Date", "20130524T000000Z"), + ("X-Amz-Expires", "999999999"), + ("X-Amz-SignedHeaders", "host"), + ]; + + let canonical_requests = [ + sig_v4::create_presigned_canonical_request( + &method, + decoded_uri_path, + query_strings_for_signing, + &headers_for_signing, + ), + sig_v4::create_presigned_canonical_request_with_raw_uri_path( + &method, + raw_uri_path, + query_strings_for_signing, + &headers_for_signing, + ), + ]; + assert_ne!(canonical_requests[0], canonical_requests[1]); + + for canonical_request in canonical_requests { + let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, "us-east-1", "s3"); + let signature = sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + let qs = OrderedQs::parse(&format!( + "{}&X-Amz-Signature={signature}", + concat!( + "X-Amz-Algorithm=AWS4-HMAC-SHA256", + "&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request", + "&X-Amz-Date=20130524T000000Z", + "&X-Amz-Expires=999999999", + "&X-Amz-SignedHeaders=host" + ) + )) + .unwrap(); + let headers = OrderedHeaders::from_slice_unchecked(&[("host", "s3.amazonaws.com")]); + + let mut body = Body::empty(); + let mut cx = SignatureContext { + auth: Some(&auth), + config: &config, + req_version: ::http::Version::HTTP_11, + req_method: &method, + req_uri: &uri, + req_body: &mut body, + qs: Some(&qs), + hs: headers, + decoded_uri_path, + raw_uri_path, + vh_bucket: None, + content_length: None, + mime: None, + decoded_content_length: None, + transformed_body: None, + multipart: None, + trailing_headers: None, + }; + + let cred = cx + .v4_check_presigned_url() + .await + .expect("valid presigned URL with a raw '=' URI path should succeed"); + assert_eq!(cred.access_key, access_key); + } + } + + #[tokio::test] + async fn v4_presigned_url_uses_http2_authority_for_signed_host() { + use crate::auth::SecretKey; + use crate::auth::SimpleAuth; + use crate::config::{S3ConfigProvider, StaticConfigProvider}; + use std::sync::Arc; + + let access_key = "AKIAIOSFODNN7EXAMPLE"; + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let auth = SimpleAuth::from_single(access_key, secret_key.clone()); + let config: Arc = Arc::new(StaticConfigProvider::default()); + + let method = Method::GET; + let uri = Uri::from_static("https://s3.amazonaws.com/test-bucket/path/sitemap.xmlage="); + let decoded_uri_path = "/test-bucket/path/sitemap.xmlage="; + let raw_uri_path = "/test-bucket/path/sitemap.xmlage="; + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let headers_for_signing = OrderedHeaders::from_slice_unchecked(&[("host", "s3.amazonaws.com")]); + let query_strings_for_signing = &[ + ("X-Amz-Algorithm", "AWS4-HMAC-SHA256"), + ("X-Amz-Credential", "AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request"), + ("X-Amz-Date", "20130524T000000Z"), + ("X-Amz-Expires", "999999999"), + ("X-Amz-SignedHeaders", "host"), + ]; + let canonical_request = sig_v4::create_presigned_canonical_request( + &method, + decoded_uri_path, + query_strings_for_signing, + &headers_for_signing, + ); + let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, "us-east-1", "s3"); + let signature = sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + let qs = OrderedQs::parse(&format!( + "{}&X-Amz-Signature={signature}", + concat!( + "X-Amz-Algorithm=AWS4-HMAC-SHA256", + "&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request", + "&X-Amz-Date=20130524T000000Z", + "&X-Amz-Expires=999999999", + "&X-Amz-SignedHeaders=host" + ) + )) + .unwrap(); + + let mut body = Body::empty(); + let mut cx = SignatureContext { + auth: Some(&auth), + config: &config, + req_version: ::http::Version::HTTP_2, + req_method: &method, + req_uri: &uri, + req_body: &mut body, + qs: Some(&qs), + hs: OrderedHeaders::from_slice_unchecked(&[]), + decoded_uri_path, + raw_uri_path, + vh_bucket: None, + content_length: None, + mime: None, + decoded_content_length: None, + transformed_body: None, + multipart: None, + trailing_headers: None, + }; + + let cred = cx + .v4_check_presigned_url() + .await + .expect("HTTP/2 authority should be used for a signed host header"); + assert_eq!(cred.access_key, access_key); + } + + #[tokio::test] + async fn v4_header_auth_accepts_standard_and_raw_uri_path_signatures() { + use crate::auth::SecretKey; + use crate::auth::SimpleAuth; + use crate::config::{S3ConfigProvider, StaticConfigProvider}; + use std::sync::Arc; + + let access_key = "AKIAIOSFODNN7EXAMPLE"; + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let auth = SimpleAuth::from_single(access_key, secret_key.clone()); + let config: Arc = Arc::new(StaticConfigProvider::default()); + + let method = Method::GET; + let uri = Uri::from_static("https://s3.amazonaws.com/test-bucket/path/sitemap.xmlage="); + let decoded_uri_path = "/test-bucket/path/sitemap.xmlage="; + let raw_uri_path = "/test-bucket/path/sitemap.xmlage="; + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let headers_for_signing = OrderedHeaders::from_slice_unchecked(&[ + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "UNSIGNED-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ]); + + let canonical_requests = [ + sig_v4::create_canonical_request( + &method, + decoded_uri_path, + &[] as &[(&str, &str)], + &headers_for_signing, + sig_v4::Payload::Unsigned, + ), + sig_v4::create_canonical_request_with_raw_uri_path( + &method, + raw_uri_path, + &[] as &[(&str, &str)], + &headers_for_signing, + sig_v4::Payload::Unsigned, + ), + ]; + + for canonical_request in canonical_requests { + let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, "us-east-1", "s3"); + let signature = sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + let authorization = format!( + "AWS4-HMAC-SHA256 Credential={access_key}/20130524/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature={signature}" + ); + let headers = OrderedHeaders::from_slice_unchecked(&[ + ("authorization", authorization.as_str()), + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "UNSIGNED-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ]); + + let mut body = Body::empty(); + let mut cx = SignatureContext { + auth: Some(&auth), + config: &config, + req_version: ::http::Version::HTTP_11, + req_method: &method, + req_uri: &uri, + req_body: &mut body, + qs: None, + hs: headers, + decoded_uri_path, + raw_uri_path, + vh_bucket: None, + content_length: Some(0), + mime: None, + decoded_content_length: None, + transformed_body: None, + multipart: None, + trailing_headers: None, + }; + + let cred = cx + .v4_check_header_auth() + .await + .expect("valid SigV4 auth with a raw '=' URI path should succeed"); + assert_eq!(cred.access_key, access_key); + } + } + + #[tokio::test] + async fn v4_header_auth_uses_http2_authority_for_signed_host() { + use crate::auth::SecretKey; + use crate::auth::SimpleAuth; + use crate::config::{S3ConfigProvider, StaticConfigProvider}; + use std::sync::Arc; + + let access_key = "AKIAIOSFODNN7EXAMPLE"; + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let auth = SimpleAuth::from_single(access_key, secret_key.clone()); + let config: Arc = Arc::new(StaticConfigProvider::default()); + + let method = Method::GET; + let uri = Uri::from_static("https://s3.amazonaws.com/test-bucket/path/sitemap.xmlage="); + let decoded_uri_path = "/test-bucket/path/sitemap.xmlage="; + let raw_uri_path = "/test-bucket/path/sitemap.xmlage="; + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let headers_for_signing = OrderedHeaders::from_slice_unchecked(&[ + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "UNSIGNED-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ]); + let canonical_request = sig_v4::create_canonical_request( + &method, + decoded_uri_path, + &[] as &[(&str, &str)], + &headers_for_signing, + sig_v4::Payload::Unsigned, + ); + let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, "us-east-1", "s3"); + let signature = sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + let authorization = format!( + "AWS4-HMAC-SHA256 Credential={access_key}/20130524/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature={signature}" + ); + let headers = OrderedHeaders::from_slice_unchecked(&[ + ("authorization", authorization.as_str()), + ("x-amz-content-sha256", "UNSIGNED-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ]); + + let mut body = Body::empty(); + let mut cx = SignatureContext { + auth: Some(&auth), + config: &config, + req_version: ::http::Version::HTTP_2, + req_method: &method, + req_uri: &uri, + req_body: &mut body, + qs: None, + hs: headers, + decoded_uri_path, + raw_uri_path, + vh_bucket: None, + content_length: Some(0), + mime: None, + decoded_content_length: None, + transformed_body: None, + multipart: None, + trailing_headers: None, + }; + + let cred = cx + .v4_check_header_auth() + .await + .expect("HTTP/2 authority should be used for a signed host header"); + assert_eq!(cred.access_key, access_key); + } + + #[tokio::test] + async fn v4_header_auth_raw_uri_path_signature_seeds_streaming_body() { + use crate::auth::SecretKey; + use crate::auth::SimpleAuth; + use crate::config::{S3ConfigProvider, StaticConfigProvider}; + use bytes::Bytes; + use std::sync::Arc; + + let access_key = "AKIAIOSFODNN7EXAMPLE"; + let secret_key: SecretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY".into(); + let auth = SimpleAuth::from_single(access_key, secret_key.clone()); + let config: Arc = Arc::new(StaticConfigProvider::default()); + + let method = Method::PUT; + let uri = Uri::from_static("https://s3.amazonaws.com/test-bucket/path/sitemap.xmlage="); + let decoded_uri_path = "/test-bucket/path/sitemap.xmlage="; + let raw_uri_path = "/test-bucket/path/sitemap.xmlage="; + let amz_date = AmzDate::parse("20130524T000000Z").unwrap(); + let chunk_data = Bytes::from_static(b"hello"); + let decoded_content_length = chunk_data.len(); + let headers_for_signing = OrderedHeaders::from_slice_unchecked(&[ + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ("x-amz-decoded-content-length", "5"), + ]); + + let standard_canonical_request = sig_v4::create_canonical_request( + &method, + decoded_uri_path, + &[] as &[(&str, &str)], + &headers_for_signing, + sig_v4::Payload::MultipleChunks, + ); + let raw_canonical_request = sig_v4::create_canonical_request_with_raw_uri_path( + &method, + raw_uri_path, + &[] as &[(&str, &str)], + &headers_for_signing, + sig_v4::Payload::MultipleChunks, + ); + assert_ne!(standard_canonical_request, raw_canonical_request); + + let seed_string_to_sign = sig_v4::create_string_to_sign(&raw_canonical_request, &amz_date, "us-east-1", "s3"); + let seed_signature = sig_v4::calculate_signature(&seed_string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + + let chunk_string_to_sign = + sig_v4::create_chunk_string_to_sign(&amz_date, "us-east-1", "s3", &seed_signature, std::slice::from_ref(&chunk_data)); + let chunk_signature = sig_v4::calculate_signature(&chunk_string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + let final_string_to_sign = sig_v4::create_chunk_string_to_sign(&amz_date, "us-east-1", "s3", &chunk_signature, &[]); + let final_signature = sig_v4::calculate_signature(&final_string_to_sign, &secret_key, &amz_date, "us-east-1", "s3"); + + let mut streaming_body = Vec::new(); + streaming_body.extend_from_slice(format!("{:x};chunk-signature={chunk_signature}\r\n", chunk_data.len()).as_bytes()); + streaming_body.extend_from_slice(&chunk_data); + streaming_body.extend_from_slice(b"\r\n"); + streaming_body.extend_from_slice(format!("0;chunk-signature={final_signature}\r\n\r\n").as_bytes()); + let content_length = u64::try_from(streaming_body.len()).unwrap(); + + let authorization = format!( + "AWS4-HMAC-SHA256 Credential={access_key}/20130524/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length, Signature={seed_signature}" + ); + let headers = OrderedHeaders::from_slice_unchecked(&[ + ("authorization", authorization.as_str()), + ("host", "s3.amazonaws.com"), + ("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"), + ("x-amz-date", "20130524T000000Z"), + ("x-amz-decoded-content-length", "5"), + ]); + + let mut body = Body::from(Bytes::from(streaming_body)); + let mut cx = SignatureContext { + auth: Some(&auth), + config: &config, + req_version: ::http::Version::HTTP_11, + req_method: &method, + req_uri: &uri, + req_body: &mut body, + qs: None, + hs: headers, + decoded_uri_path, + raw_uri_path, + vh_bucket: None, + content_length: Some(content_length), + mime: None, + decoded_content_length: Some(decoded_content_length), + transformed_body: None, + multipart: None, + trailing_headers: None, + }; + + let cred = cx + .v4_check_header_auth() + .await + .expect("valid streaming SigV4 auth with a raw '=' URI path should succeed"); + assert_eq!(cred.access_key, access_key); + + let mut transformed_body = cx.transformed_body.take().expect("streaming body should be transformed"); + let decoded_body = transformed_body + .store_all_limited(decoded_content_length) + .await + .expect("raw-path seed signature should validate aws-chunked body"); + assert_eq!(decoded_body, chunk_data); + } + /// `SigV2` does not carry region in the credential scope, so `CredentialsExt.region` /// must always be `None` and `service` must always be `Some("s3")`. /// @@ -903,7 +1403,8 @@ file content\r\n\ req_body: &mut body, qs: None, hs, - decoded_uri_path: "/test-bucket/test-key".to_owned(), + decoded_uri_path: "/test-bucket/test-key", + raw_uri_path: "/test-bucket/test-key", vh_bucket: None, content_length: None, mime: None, diff --git a/crates/s3s/src/ops/tests.rs b/crates/s3s/src/ops/tests.rs index 33954d57..5cfcb561 100644 --- a/crates/s3s/src/ops/tests.rs +++ b/crates/s3s/src/ops/tests.rs @@ -392,7 +392,8 @@ async fn presigned_url_expires_0_should_be_expired() { req_body: &mut body, qs: Some(&qs), hs: OrderedHeaders::from_slice_unchecked(&[]), - decoded_uri_path: "/test.txt".to_owned(), + decoded_uri_path: "/test.txt", + raw_uri_path: "/test.txt", vh_bucket: None, content_length: None, mime: None, diff --git a/crates/s3s/src/sig_v4/methods.rs b/crates/s3s/src/sig_v4/methods.rs index 5ffecd36..478ef248 100644 --- a/crates/s3s/src/sig_v4/methods.rs +++ b/crates/s3s/src/sig_v4/methods.rs @@ -115,14 +115,13 @@ impl Payload<'_> { } } -/// create canonical request -#[must_use] -pub fn create_canonical_request( +fn create_canonical_request_with_uri_mode( method: &Method, uri_path: &str, decoded_query_strings: &[(impl AsRef, impl AsRef)], signed_headers: &OrderedHeaders<'_>, payload: Payload<'_>, + raw_uri_path: bool, ) -> String { let mut ans = String::with_capacity(256); @@ -134,7 +133,11 @@ pub fn create_canonical_request( { // \n - uri_encode(&mut ans, uri_path, false); + if raw_uri_path { + ans.push_str(uri_path); + } else { + uri_encode(&mut ans, uri_path, false); + } ans.push('\n'); } @@ -246,6 +249,28 @@ pub fn create_canonical_request( ans } +/// create canonical request +#[must_use] +pub fn create_canonical_request( + method: &Method, + uri_path: &str, + decoded_query_strings: &[(impl AsRef, impl AsRef)], + signed_headers: &OrderedHeaders<'_>, + payload: Payload<'_>, +) -> String { + create_canonical_request_with_uri_mode(method, uri_path, decoded_query_strings, signed_headers, payload, false) +} + +pub(crate) fn create_canonical_request_with_raw_uri_path( + method: &Method, + raw_uri_path: &str, + decoded_query_strings: &[(impl AsRef, impl AsRef)], + signed_headers: &OrderedHeaders<'_>, + payload: Payload<'_>, +) -> String { + create_canonical_request_with_uri_mode(method, raw_uri_path, decoded_query_strings, signed_headers, payload, true) +} + /// create string to sign #[must_use] pub fn create_string_to_sign(canonical_request: &str, amz_date: &AmzDate, region: &str, service: &str) -> String { @@ -398,12 +423,12 @@ pub fn calculate_signature( hex(hmac_sha256(signing_key, string_to_sign)) } -/// create presigned canonical request -pub fn create_presigned_canonical_request( +fn create_presigned_canonical_request_with_uri_mode( method: &Method, uri_path: &str, decoded_query_strings: &[(impl AsRef, impl AsRef)], signed_headers: &OrderedHeaders<'_>, + raw_uri_path: bool, ) -> String { let mut ans = String::with_capacity(256); { @@ -413,7 +438,11 @@ pub fn create_presigned_canonical_request( } { // \n - uri_encode(&mut ans, uri_path, false); + if raw_uri_path { + ans.push_str(uri_path); + } else { + uri_encode(&mut ans, uri_path, false); + } ans.push('\n'); } { @@ -515,6 +544,25 @@ pub fn create_presigned_canonical_request( ans } +/// create presigned canonical request +pub fn create_presigned_canonical_request( + method: &Method, + uri_path: &str, + decoded_query_strings: &[(impl AsRef, impl AsRef)], + signed_headers: &OrderedHeaders<'_>, +) -> String { + create_presigned_canonical_request_with_uri_mode(method, uri_path, decoded_query_strings, signed_headers, false) +} + +pub(crate) fn create_presigned_canonical_request_with_raw_uri_path( + method: &Method, + raw_uri_path: &str, + decoded_query_strings: &[(impl AsRef, impl AsRef)], + signed_headers: &OrderedHeaders<'_>, +) -> String { + create_presigned_canonical_request_with_uri_mode(method, raw_uri_path, decoded_query_strings, signed_headers, true) +} + #[cfg(test)] mod tests { use super::*; From 2f3c9733fecadf161ebda82088b31324c85e2e38 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 17 May 2026 19:37:25 +0300 Subject: [PATCH 13/20] fix(s3s-fs): honor MetadataDirective::Replace in copy_object (#586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(s3s-fs): honor MetadataDirective::Replace in copy_object `copy_object` previously ignored both `input.metadata_directive` and the request's metadata fields, unconditionally copying the source's metadata sidecar to the destination. Real S3 honors the directive: when `metadata_directive == REPLACE`, the destination's metadata is built fresh from the request (`metadata`, `content_type`, `content_encoding`, `content_disposition`, `content_language`, `cache_control`, `expires`, `website_redirect_location`) and the source's metadata is dropped. This matters in particular for the in-place metadata-update pattern (`CopyObject` same bucket+key with `MetadataDirective: REPLACE`), which is the canonical AWS-side way to rotate an object's metadata without rewriting its bytes — until this fix s3s-fs silently preserved the old metadata regardless of what the caller asked for. The default (header absent) and explicit `COPY` continue to copy the sidecar verbatim, matching prior behaviour. Includes two regression tests: - `test_copy_object_metadata_directive_replace` — REPLACE installs the request's `content_type` and `x-amz-meta-*` on the destination. - `test_copy_object_metadata_directive_copy_ignores_request_fields` — COPY propagates source metadata and ignores the request override. Both validated empirically: the REPLACE test fails on `main` with `left: "application/octet-stream", right: "application/pdf"` and passes after the fix. See https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html for the documented contract. Signed-off-by: Roland From * add tests --------- Signed-off-by: Roland From Co-authored-by: Nugine --- crates/s3s-fs/src/s3.rs | 36 ++++++- crates/s3s-fs/tests/it_aws.rs | 179 ++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 4 deletions(-) diff --git a/crates/s3s-fs/src/s3.rs b/crates/s3s-fs/src/s3.rs index bcf1370d..213794cd 100644 --- a/crates/s3s-fs/src/s3.rs +++ b/crates/s3s-fs/src/s3.rs @@ -158,10 +158,38 @@ impl S3 for FileSystem { None => self.get_md5_sum(&input.bucket, &input.key).await?, }; - let src_metadata_path = self.get_metadata_path(bucket, key, None)?; - if src_metadata_path.exists() { - let dst_metadata_path = self.get_metadata_path(&input.bucket, &input.key, None)?; - let _ = try_!(fs::copy(src_metadata_path, dst_metadata_path).await); + // `MetadataDirective` defaults to `COPY` per AWS API: when the + // header is absent the destination should inherit the source's + // metadata sidecar verbatim. When set to `REPLACE`, the + // destination's metadata is built fresh from the request and + // anything from the source is dropped (matching the behaviour + // documented at + // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html). + let replace_metadata = input + .metadata_directive + .as_ref() + .is_some_and(|d| d.as_str() == MetadataDirective::REPLACE); + + if replace_metadata { + let mut dst_attrs = crate::fs::ObjectAttributes { + user_metadata: input.metadata, + content_encoding: input.content_encoding, + content_type: input.content_type, + content_disposition: input.content_disposition, + content_language: input.content_language, + cache_control: input.cache_control, + expires: None, + website_redirect_location: input.website_redirect_location, + }; + dst_attrs.set_expires_timestamp(input.expires); + self.save_object_attributes(&input.bucket, &input.key, &dst_attrs, None) + .await?; + } else { + let src_metadata_path = self.get_metadata_path(bucket, key, None)?; + if src_metadata_path.exists() { + let dst_metadata_path = self.get_metadata_path(&input.bucket, &input.key, None)?; + let _ = try_!(fs::copy(src_metadata_path, dst_metadata_path).await); + } } { diff --git a/crates/s3s-fs/tests/it_aws.rs b/crates/s3s-fs/tests/it_aws.rs index 22b31be1..f844150d 100644 --- a/crates/s3s-fs/tests/it_aws.rs +++ b/crates/s3s-fs/tests/it_aws.rs @@ -1610,6 +1610,185 @@ async fn test_copy_object_nested_dst() -> Result<()> { Ok(()) } +/// `MetadataDirective: REPLACE` must drop the source metadata and +/// install the request's metadata + `content_type` on the destination. +/// Before the fix, `copy_object` ignored both `metadata_directive` +/// and `metadata` and unconditionally copied the source sidecar +/// verbatim. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_metadata_directive_replace() -> Result<()> { + use aws_sdk_s3::types::MetadataDirective; + + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-meta-replace-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + + create_bucket(&c, bucket).await?; + + let src_key = "src.bin"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(b"x")) + .content_type("application/octet-stream") + .metadata("origin", "v1") + .metadata("rev", "1") + .metadata("source-only", "keep-out") + .send() + .await?; + + let dst_key = "dst.bin"; + let copy_source = format!("{bucket}/{src_key}"); + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .metadata_directive(MetadataDirective::Replace) + .content_type("application/pdf") + .metadata("origin", "v2") + .metadata("rev", "2") + .send() + .await?; + + let head = c.head_object().bucket(bucket).key(dst_key).send().await?; + assert_eq!( + head.content_type().unwrap_or(""), + "application/pdf", + "REPLACE must install the request's content_type on the destination" + ); + let dst_meta = head.metadata().cloned().unwrap_or_default(); + assert_eq!(dst_meta.get("origin").map(String::as_str), Some("v2")); + assert_eq!(dst_meta.get("rev").map(String::as_str), Some("2")); + assert_eq!( + dst_meta.get("source-only"), + None, + "REPLACE must drop metadata that only exists on the source object" + ); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// Omitting `MetadataDirective` must use S3's default `COPY` behavior: +/// propagate source metadata and ignore replacement fields from the request. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_metadata_directive_default_copies_source() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-meta-default-copy-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + + create_bucket(&c, bucket).await?; + + let src_key = "src.bin"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(b"x")) + .content_type("application/octet-stream") + .metadata("origin", "v1") + .send() + .await?; + + let dst_key = "dst.bin"; + let copy_source = format!("{bucket}/{src_key}"); + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .content_type("application/pdf") + .metadata("origin", "v2") + .send() + .await?; + + let head = c.head_object().bucket(bucket).key(dst_key).send().await?; + assert_eq!( + head.content_type().unwrap_or(""), + "application/octet-stream", + "default MetadataDirective must propagate the source content_type" + ); + let dst_meta = head.metadata().cloned().unwrap_or_default(); + assert_eq!( + dst_meta.get("origin").map(String::as_str), + Some("v1"), + "default MetadataDirective must propagate the source metadata" + ); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// `MetadataDirective: COPY` (the default) must propagate the source +/// metadata to the destination, ignoring any metadata fields supplied +/// in the request — exactly as documented at +/// . +#[allow(clippy::doc_markdown)] +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_metadata_directive_copy_ignores_request_fields() -> Result<()> { + use aws_sdk_s3::types::MetadataDirective; + + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-meta-copy-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + + create_bucket(&c, bucket).await?; + + let src_key = "src.bin"; + c.put_object() + .bucket(bucket) + .key(src_key) + .body(ByteStream::from_static(b"x")) + .content_type("application/octet-stream") + .metadata("origin", "v1") + .send() + .await?; + + let dst_key = "dst.bin"; + let copy_source = format!("{bucket}/{src_key}"); + c.copy_object() + .bucket(bucket) + .key(dst_key) + .copy_source(©_source) + .metadata_directive(MetadataDirective::Copy) + .content_type("application/pdf") // expected to be ignored under COPY + .metadata("origin", "v2") + .send() + .await?; + + let head = c.head_object().bucket(bucket).key(dst_key).send().await?; + assert_eq!( + head.content_type().unwrap_or(""), + "application/octet-stream", + "COPY must propagate the source content_type and ignore the request override" + ); + let dst_meta = head.metadata().cloned().unwrap_or_default(); + assert_eq!( + dst_meta.get("origin").map(String::as_str), + Some("v1"), + "COPY must propagate the source metadata, not the request fields" + ); + + delete_object(&c, bucket, src_key).await?; + delete_object(&c, bucket, dst_key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + /// Test conditional copy with `x-amz-copy-source-if-match`. #[tokio::test] #[tracing::instrument] From cf9e3b9685e2628e1bdf970275785ffdf185c84e Mon Sep 17 00:00:00 2001 From: Taylor Date: Sun, 17 May 2026 10:32:21 -0700 Subject: [PATCH 14/20] feat(s3s-fs): support If-Match conditional on PutObject (#588) * feat(s3s-fs): support If-Match conditional on PutObject Honor the `If-Match` header on `PutObject` so clients can perform compare-and-swap writes against the stored ETag. Mirrors the existing `If-None-Match` handling and the `copy_object` precondition pattern: absent objects fail with `PreconditionFailed`, and an explicit ETag condition falls back to MD5 only when no stored ETag is available. * Update CHANGELOG to remove s3s-fs note Removed the note about honoring `If-Match` on `PutObject` for compare-and-swap writes under s3s-fs. * add tests --------- Co-authored-by: Nugine --- crates/s3s-fs/src/s3.rs | 28 ++- crates/s3s-fs/tests/it_aws.rs | 339 ++++++++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+), 7 deletions(-) diff --git a/crates/s3s-fs/src/s3.rs b/crates/s3s-fs/src/s3.rs index 213794cd..53bb3077 100644 --- a/crates/s3s-fs/src/s3.rs +++ b/crates/s3s-fs/src/s3.rs @@ -661,20 +661,36 @@ impl S3 for FileSystem { cache_control, expires, website_redirect_location, + if_match, if_none_match, .. } = input; let Some(body) = body else { return Err(s3_error!(IncompleteBody)) }; - // Check If-None-Match condition - // If-None-Match: * means "only create if the object doesn't exist" + // Check conditional headers before modifying any state. + // If-None-Match: * means "only create if the object doesn't exist". + // If-Match: means "only overwrite if ETag matches" (CAS). + let object_path = self.get_object_path(&bucket, &key)?; if let Some(ref condition) = if_none_match && condition.is_any() + && object_path.exists() { - let object_path = self.get_object_path(&bucket, &key)?; - if object_path.exists() { - return Err(s3_error!(PreconditionFailed, "Object already exists")); + return Err(s3_error!(PreconditionFailed, "Object already exists")); + } + if let Some(ref condition) = if_match { + if !object_path.exists() { + return Err(s3_error!(PreconditionFailed, "Object does not exist")); + } + if let ETagCondition::ETag(expected) = condition { + let info = self.load_internal_info(&bucket, &key).await?; + let etag_value = match info.as_ref().and_then(crate::checksum::load_e_tag) { + Some(v) => v, + None => self.get_md5_sum(&bucket, &key).await?, + }; + if !ETag::Strong(etag_value).strong_cmp(expected) { + return Err(s3_error!(PreconditionFailed, "ETag does not match")); + } } } @@ -711,13 +727,11 @@ impl S3 for FileSystem { { return Err(s3_error!(UnexpectedContent, "Unexpected request body when creating a directory object.")); } - let object_path = self.get_object_path(&bucket, &key)?; try_!(fs::create_dir_all(&object_path).await); let output = PutObjectOutput::default(); return Ok(S3Response::new(output)); } - let object_path = self.get_object_path(&bucket, &key)?; let mut file_writer = self.prepare_file_write(&object_path).await?; let mut md5_hash = Md5::new(); diff --git a/crates/s3s-fs/tests/it_aws.rs b/crates/s3s-fs/tests/it_aws.rs index f844150d..cf90165d 100644 --- a/crates/s3s-fs/tests/it_aws.rs +++ b/crates/s3s-fs/tests/it_aws.rs @@ -1563,6 +1563,345 @@ async fn test_if_none_match_wildcard() -> Result<()> { Ok(()) } +/// Test that `PutObject` with `If-Match: *` succeeds when the object exists +/// and fails with `PreconditionFailed` (412) when the object is absent. +#[tokio::test] +#[tracing::instrument] +async fn test_put_object_if_match_wildcard() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("if-match-wc-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + let key = "test-file.txt"; + let initial = "initial content"; + let updated = "updated content"; + + create_bucket(&c, bucket).await?; + + // Test 1: PUT with If-Match: * should fail when the object doesn't exist + { + let body = ByteStream::from_static(initial.as_bytes()); + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(body) + .if_match("*") + .send() + .await + .expect_err("Expected If-Match: * on absent object to fail"); + + let service_err = err.into_service_error(); + assert_eq!( + service_err.code(), + Some("PreconditionFailed"), + "Expected PreconditionFailed, got: {:?}", + service_err.code() + ); + } + + // Seed the object so the next steps have something to match against. + c.put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(initial.as_bytes())) + .send() + .await?; + + // Test 2: PUT with If-Match: * should succeed when the object exists + { + let body = ByteStream::from_static(updated.as_bytes()); + c.put_object() + .bucket(bucket) + .key(key) + .body(body) + .if_match("*") + .send() + .await + .expect("Expected If-Match: * on existing object to succeed"); + } + + { + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), updated.as_bytes()); + } + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +/// Test that `PutObject` with `If-Match: ` overwrites only when the +/// stored `ETag` matches and returns `PreconditionFailed` (412) otherwise. +#[tokio::test] +#[tracing::instrument] +async fn test_put_object_if_match_etag() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("if-match-etag-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + let key = "test-file.txt"; + let initial = "initial content"; + let updated = "updated content"; + + create_bucket(&c, bucket).await?; + + // Test 1: PUT with If-Match: should fail when the object doesn't exist + { + let body = ByteStream::from_static(initial.as_bytes()); + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(body) + .if_match("\"some-etag\"") + .send() + .await + .expect_err("Expected If-Match on absent object to fail"); + + let service_err = err.into_service_error(); + assert_eq!( + service_err.code(), + Some("PreconditionFailed"), + "Expected PreconditionFailed, got: {:?}", + service_err.code() + ); + } + + // Seed the object and capture the real ETag. + let initial_etag = { + let body = ByteStream::from_static(initial.as_bytes()); + let result = c.put_object().bucket(bucket).key(key).body(body).send().await?; + result.e_tag().expect("put_object should return e_tag").to_owned() + }; + + // Test 2: PUT with a wrong ETag should fail and not overwrite + { + let body = ByteStream::from_static(updated.as_bytes()); + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(body) + .if_match("\"wrong-etag-value\"") + .send() + .await + .expect_err("Expected If-Match with wrong ETag to fail"); + + let service_err = err.into_service_error(); + assert_eq!( + service_err.code(), + Some("PreconditionFailed"), + "Expected PreconditionFailed, got: {:?}", + service_err.code() + ); + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), initial.as_bytes(), "Object should not be overwritten"); + } + + // Test 3: PUT with the matching ETag should succeed and replace the body + { + let body = ByteStream::from_static(updated.as_bytes()); + c.put_object() + .bucket(bucket) + .key(key) + .body(body) + .if_match(&initial_etag) + .send() + .await + .expect("Expected If-Match with matching ETag to succeed"); + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), updated.as_bytes()); + } + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +#[tokio::test] +#[tracing::instrument] +async fn test_put_object_if_match_multipart_etag() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("if-match-multipart-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + let key = "multipart-source.txt"; + let initial = b"multipart initial content"; + let updated = b"updated through put object"; + + create_bucket(&c, bucket).await?; + + let multipart_etag = { + let (upload_id, upload_parts) = do_multipart_upload(&c, bucket, key, initial).await?; + let upload = CompletedMultipartUpload::builder().set_parts(Some(upload_parts)).build(); + let result = c + .complete_multipart_upload() + .bucket(bucket) + .key(key) + .upload_id(&upload_id) + .multipart_upload(upload) + .send() + .await?; + result + .e_tag() + .expect("complete_multipart_upload should return e_tag") + .to_owned() + }; + assert!(multipart_etag.contains('-'), "expected multipart ETag format"); + + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(b"wrong overwrite")) + .if_match("\"wrong-etag-value\"") + .send() + .await + .expect_err("wrong ETag should fail"); + assert_eq!(err.into_service_error().code(), Some("PreconditionFailed")); + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), initial); + + c.put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(updated)) + .if_match(&multipart_etag) + .send() + .await?; + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), updated); + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +#[tokio::test] +#[tracing::instrument] +async fn test_put_object_if_match_legacy_md5_fallback() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("if-match-legacy-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + let key = "legacy-object.txt"; + let initial = b"legacy initial content"; + let updated = b"legacy updated content"; + + create_bucket(&c, bucket).await?; + + let initial_etag = { + let result = c + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(initial)) + .send() + .await?; + result.e_tag().expect("put_object should return e_tag").to_owned() + }; + + let encode = |s: &str| base64_simd::URL_SAFE_NO_PAD.encode_to_string(s); + let internal_info_path = + std::path::Path::new(FS_ROOT).join(format!(".bucket-{}.object-{}.internal.json", encode(bucket), encode(key))); + fs::remove_file(internal_info_path)?; + + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(b"wrong overwrite")) + .if_match("\"wrong-etag-value\"") + .send() + .await + .expect_err("wrong ETag should fail through MD5 fallback"); + assert_eq!(err.into_service_error().code(), Some("PreconditionFailed")); + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), initial); + + c.put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(updated)) + .if_match(&initial_etag) + .send() + .await?; + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), updated); + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + +#[tokio::test] +#[tracing::instrument] +async fn test_put_object_if_match_rejects_weak_etag() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("if-match-weak-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + let key = "weak-etag.txt"; + let initial = b"weak etag initial content"; + + create_bucket(&c, bucket).await?; + + let initial_etag = { + let result = c + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(initial)) + .send() + .await?; + result.e_tag().expect("put_object should return e_tag").to_owned() + }; + let weak_etag = format!("W/{initial_etag}"); + + let err = c + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(b"weak overwrite")) + .if_match(weak_etag) + .send() + .await + .expect_err("weak ETag must not satisfy If-Match"); + assert_eq!(err.into_service_error().code(), Some("PreconditionFailed")); + + let result = c.get_object().bucket(bucket).key(key).send().await?; + let body = result.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), initial); + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + /// Regression test for /// /// `copy_object` should create parent directories when the destination key contains "/" From 437ccc2864e5e48af02df14270d2f52e32c2b09e Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 17 May 2026 21:36:13 +0300 Subject: [PATCH 15/20] fix(s3s-fs): preserve content on CopyObject self-replace (#585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(s3s-fs): preserve content on CopyObject self-replace `copy_object` calls `tokio::fs::copy(&src_path, &dst_path)`, which internally opens dst with `O_TRUNC` *before* it begins reading src. When src and dst resolve to the same path (a CopyObject self-replace — the canonical AWS pattern for updating an object's metadata in place via `MetadataDirective: REPLACE`), the destination file is truncated to zero bytes before any data is read. The subsequent reads from src then return an empty stream, so the file ends up empty. The same shape applies to the metadata sidecar copy a few lines later: `fs::copy(src_metadata_path, dst_metadata_path)` with src == dst would zero the JSON sidecar in place. Fix: detect `src_path == dst_path` (and the metadata-sidecar counterpart) and skip the `fs::copy` entirely — the bytes are already in place, only the surrounding state (etag, internal info, optional new metadata) needs updating. Includes a regression test that PUTs an object, copies it onto itself, and asserts the content survives. Without the fix the test fails with `body checksum mismatch ... expected 5d81f958 but it was 00000000`. Signed-off-by: Roland From * fix(s3s-fs): update self-replace copy timestamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fmt --------- Signed-off-by: Roland From Co-authored-by: Nugine Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/s3s-fs/src/s3.rs | 28 ++++++++++++---- crates/s3s-fs/tests/it_aws.rs | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/crates/s3s-fs/src/s3.rs b/crates/s3s-fs/src/s3.rs index 53bb3077..f56244bf 100644 --- a/crates/s3s-fs/src/s3.rs +++ b/crates/s3s-fs/src/s3.rs @@ -11,11 +11,13 @@ use s3s::s3_error; use s3s::{S3Request, S3Response}; use std::collections::VecDeque; +use std::fs::FileTimes; use std::io; use std::ops::Neg; use std::ops::Not; use std::path::Component; use std::path::{Path, PathBuf}; +use std::time::SystemTime; use tokio::fs; use tokio::io::AsyncReadExt; @@ -143,12 +145,20 @@ impl S3 for FileSystem { try_!(fs::create_dir_all(&dir_path).await); } - let _ = try_!(fs::copy(&src_path, &dst_path).await); - - debug!(from = %src_path.display(), to = %dst_path.display(), "copy file"); - - let dst_metadata = try_!(fs::metadata(&dst_path).await); - let dst_last_modified = Timestamp::from(try_!(dst_metadata.modified())); + // `fs::copy(p, p)` truncates the file before reading it, so self-replace + // must preserve bytes in place while still updating LastModified. + let dst_last_modified = if src_path == dst_path { + let now = SystemTime::now(); + let file = try_!(std::fs::OpenOptions::new().write(true).open(&dst_path)); + try_!(file.set_times(FileTimes::new().set_modified(now))); + debug!(path = %dst_path.display(), "replace file in place"); + Timestamp::from(now) + } else { + let _ = try_!(fs::copy(&src_path, &dst_path).await); + debug!(from = %src_path.display(), to = %dst_path.display(), "copy file"); + let dst_metadata = try_!(fs::metadata(&dst_path).await); + Timestamp::from(try_!(dst_metadata.modified())) + }; // Derive the destination ETag from the source ETag when available. // This preserves non-MD5 ETag formats (e.g., multipart `{hash}-{part_count}`) @@ -188,7 +198,11 @@ impl S3 for FileSystem { let src_metadata_path = self.get_metadata_path(bucket, key, None)?; if src_metadata_path.exists() { let dst_metadata_path = self.get_metadata_path(&input.bucket, &input.key, None)?; - let _ = try_!(fs::copy(src_metadata_path, dst_metadata_path).await); + // Same self-replace guard as for the payload above — `fs::copy` + // would zero the metadata sidecar when src == dst. + if src_metadata_path != dst_metadata_path { + let _ = try_!(fs::copy(src_metadata_path, dst_metadata_path).await); + } } } diff --git a/crates/s3s-fs/tests/it_aws.rs b/crates/s3s-fs/tests/it_aws.rs index cf90165d..a5228d74 100644 --- a/crates/s3s-fs/tests/it_aws.rs +++ b/crates/s3s-fs/tests/it_aws.rs @@ -1949,6 +1949,67 @@ async fn test_copy_object_nested_dst() -> Result<()> { Ok(()) } +/// Regression test: `CopyObject` with `src == dst` (self-replace) must +/// preserve the on-disk content. AWS S3 supports this shape as the +/// canonical way to update an object's metadata in place. Before the +/// fix, `tokio::fs::copy(src, dst)` opened the destination with +/// `O_TRUNC` before reading the source, zeroing the file. +#[tokio::test] +#[tracing::instrument] +async fn test_copy_object_self_replace_preserves_content() -> Result<()> { + let _guard = serial().await; + + let c = Client::new(config()); + let bucket = format!("test-self-replace-{}", Uuid::new_v4()); + let bucket = bucket.as_str(); + + create_bucket(&c, bucket).await?; + + let key = "obj.bin"; + let content = "original content that must survive a self-replace"; + c.put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(content.as_bytes())) + .send() + .await?; + + let before_head = c.head_object().bucket(bucket).key(key).send().await?; + let before_last_modified = before_head + .last_modified() + .expect("head_object should return last_modified") + .to_owned(); + + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + let copy_source = format!("{bucket}/{key}"); + c.copy_object() + .bucket(bucket) + .key(key) + .copy_source(©_source) + .send() + .await?; + + let after_head = c.head_object().bucket(bucket).key(key).send().await?; + let after_last_modified = after_head + .last_modified() + .expect("head_object should return last_modified") + .to_owned(); + assert!( + after_last_modified > before_last_modified, + "CopyObject self-replace must update LastModified" + ); + + let got = c.get_object().bucket(bucket).key(key).send().await?; + let body = got.body.collect().await?.into_bytes(); + assert_eq!(body.as_ref(), content.as_bytes(), "CopyObject self-replace must not zero the file"); + + delete_object(&c, bucket, key).await?; + delete_bucket(&c, bucket).await?; + + Ok(()) +} + /// `MetadataDirective: REPLACE` must drop the source metadata and /// install the request's metadata + `content_type` on the destination. /// Before the fix, `copy_object` ignored both `metadata_directive` From e727c40fc213b5ce68d9b4226bd90cda1f8e10cc Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 17:52:22 +0800 Subject: [PATCH 16/20] chore(deps): unlock opendal, update to 0.56.0 (#590) Agent-Logs-Url: https://github.com/s3s-project/s3s/sessions/3a694a7d-b7c9-4e47-8635-b6b7bd9291d5 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Nugine <30099658+Nugine@users.noreply.github.com> --- Cargo.lock | 9 ++++++--- Cargo.toml | 2 +- crates/s3s-fs/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 192023b7..02901486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2104,7 +2104,8 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opendal" version = "0.56.0" -source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b31d3d8e99a85d83b73ec26647f5607b80578ed9375810b6e44ffa3590a236" dependencies = [ "opendal-core", "opendal-service-s3", @@ -2113,7 +2114,8 @@ dependencies = [ [[package]] name = "opendal-core" version = "0.56.0" -source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1849dd2687e173e776d3af5fce1ba3ae47b9dd37a09d1c4deba850ef45fe00ca" dependencies = [ "anyhow", "base64", @@ -2140,7 +2142,8 @@ dependencies = [ [[package]] name = "opendal-service-s3" version = "0.56.0" -source = "git+https://github.com/apache/opendal?rev=8332367dd7629bebc7759a11a5bbbd941dd060e9#8332367dd7629bebc7759a11a5bbbd941dd060e9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dadddeb9bb50b0d30927dd914c298c4ddca47e4c1cfa7674d311f0cf9b051c8" dependencies = [ "base64", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 18faa4fa..e87463a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,7 +111,7 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter", "time"] } clap = { version = "4.6.1", features = ["derive"] } # Storage backends -opendal = "0.55.0" +opendal = { version = "0.56.0", default-features = false } # AWS SDK aws-config = { version = "1.8.15", default-features = false } diff --git a/crates/s3s-fs/Cargo.toml b/crates/s3s-fs/Cargo.toml index 5ce8bcbf..a8ba8640 100644 --- a/crates/s3s-fs/Cargo.toml +++ b/crates/s3s-fs/Cargo.toml @@ -62,7 +62,7 @@ aws-sdk-sts = { workspace = true, features = ["behavior-version-latest"] } futures-util.workspace = true hyper = { workspace = true, features = ["http1", "http2"] } hyper-util = { workspace = true, features = ["server-auto", "server-graceful", "http1", "http2", "tokio"] } -opendal = { git = "https://github.com/apache/opendal", rev = "8332367dd7629bebc7759a11a5bbbd941dd060e9", default-features = false, features = ["services-s3", "executors-tokio", "reqwest-rustls-tls"] } +opendal = { workspace = true, default-features = false, features = ["services-s3", "executors-tokio", "reqwest-rustls-tls"] } s3s-aws = { version = "0.14.0-dev", path = "../s3s-aws" } tokio = { workspace = true, features = ["full"] } tracing-subscriber.workspace = true From 1e080fc025d5863f543dd06bf77d7cb3bc7cd497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 15:01:02 +0800 Subject: [PATCH 17/20] build(deps): bump idna from 3.10 to 3.15 (#591) Bumps [idna](https://github.com/kjd/idna) from 3.10 to 3.15. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.10...v3.15) --- updated-dependencies: - dependency-name: idna dependency-version: '3.15' dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 20b45880..9437b937 100644 --- a/uv.lock +++ b/uv.lock @@ -99,11 +99,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] From c693f39bbbd705df01e32dbd42e369fd37c425e8 Mon Sep 17 00:00:00 2001 From: houseme Date: Wed, 20 May 2026 18:06:30 +0800 Subject: [PATCH 18/20] build(deps): bump workspace dependencies --- Cargo.lock | 550 ++++++++++++++++--------------------- Cargo.toml | 16 +- crates/s3s-wasm/Cargo.toml | 2 +- 3 files changed, 242 insertions(+), 326 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02901486..1a432698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -88,7 +88,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -146,9 +146,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.15" +version = "1.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" +checksum = "50f156acdd2cf55f5aa53ee416c4ac851cf1222694506c0b1f78c85695e9ca9d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "zeroize", @@ -193,9 +193,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -205,9 +205,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" +checksum = "5dcd93c82209ac7413532388067dce79be5a8780c1786e5fae3df22e4dee2864" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.129.0" +version = "1.132.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4e8410fadbc0ee453145dd77a4958227b18b05bf67c2795d0a8b8596c9aa0f" +checksum = "5575840a3a6b11f6011463ebe359320dfe5b67babb5e9b06fed6ddf809a9ab40" dependencies = [ "aws-credential-types", "aws-runtime", @@ -254,23 +254,23 @@ dependencies = [ "bytes", "fastrand", "hex", - "hmac 0.12.1", + "hmac 0.13.0", "http 0.2.12", "http 1.4.0", "http-body 1.0.1", "lru", "percent-encoding", "regex-lite", - "sha2 0.10.9", + "sha2 0.11.0", "tracing", "url", ] [[package]] name = "aws-sdk-sts" -version = "1.102.0" +version = "1.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc35b7a14cabdad13795fbbbd26d5ddec0882c01492ceedf2af575aad5f37dd" +checksum = "c2249b81a2e73a8027c41c378463a81ec39b8510f184f2caab87de912af0f49b" dependencies = [ "aws-credential-types", "aws-runtime", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" +checksum = "68dc0b907359b120170613b5c09ccc61304eac3998ff6274b97d93ee6490115a" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -306,13 +306,13 @@ dependencies = [ "crypto-bigint 0.5.5", "form_urlencoded", "hex", - "hmac 0.12.1", + "hmac 0.13.0", "http 0.2.12", "http 1.4.0", "p256", "percent-encoding", "ring", - "sha2 0.10.9", + "sha2 0.11.0", "subtle", "time", "tracing", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.64.6" +version = "0.64.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6750f3dd509b0694a4377f0293ed2f9630d710b1cebe281fa8bac8f099f88bc6" +checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -344,10 +344,10 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "md-5 0.10.6", + "md-5 0.11.0", "pin-project-lite", - "sha1 0.10.6", - "sha2 0.10.9", + "sha1 0.11.0", + "sha2 0.11.0", "tracing", ] @@ -410,10 +410,12 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.5" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", ] @@ -438,15 +440,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.10.3" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028999056d2d2fd58a697232f9eec4a643cf73a71cf327690a7edad1d2af2110" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-client", "aws-smithy-observability", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "bytes", "fastrand", @@ -463,9 +466,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -490,11 +493,22 @@ dependencies = [ "syn", ] +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.0", +] + [[package]] name = "aws-smithy-types" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" dependencies = [ "base64-simd", "bytes", @@ -537,9 +551,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.14" +version = "1.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -646,9 +660,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -707,9 +721,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -717,12 +731,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -788,18 +796,18 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "cmov" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] name = "colorchoice" @@ -813,7 +821,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -912,30 +920,13 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc-fast" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" dependencies = [ - "crc", "digest 0.10.7", - "rustversion", "spin", ] @@ -997,18 +988,18 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] [[package]] name = "ctutils" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ "cmov", ] @@ -1046,13 +1037,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] @@ -1139,14 +1130,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" @@ -1371,9 +1362,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1416,9 +1407,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1457,7 +1448,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -1529,9 +1520,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] @@ -1560,16 +1551,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper", "hyper-util", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1624,12 +1614,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1637,9 +1628,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1650,9 +1641,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1664,15 +1655,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1684,15 +1675,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1722,9 +1713,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1737,7 +1728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1748,16 +1739,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1772,9 +1753,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1784,14 +1765,14 @@ dependencies = [ "portable-atomic-util", "serde_core", "wasm-bindgen", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -1815,27 +1796,32 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] @@ -1869,9 +1855,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -1893,9 +1879,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1905,9 +1891,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -1926,9 +1912,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -1971,7 +1957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2040,14 +2026,14 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2162,9 +2148,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.79" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", @@ -2193,9 +2179,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -2301,9 +2287,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -2322,9 +2308,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2385,9 +2371,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", "serde", @@ -2407,7 +2393,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -2429,7 +2415,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2472,9 +2458,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2564,7 +2550,7 @@ dependencies = [ "http 1.4.0", "log", "percent-encoding", - "quick-xml 0.39.2", + "quick-xml 0.39.4", "reqsign-core", "rust-ini", "serde", @@ -2689,9 +2675,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2704,9 +2690,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -2731,9 +2717,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -2741,9 +2727,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", @@ -2757,7 +2743,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2833,7 +2819,7 @@ dependencies = [ "std-next", "subtle", "sync_wrapper", - "thiserror 2.0.18", + "thiserror", "time", "tokio", "tokio-rustls", @@ -2928,7 +2914,7 @@ dependencies = [ "serde", "serde_json", "std-next", - "thiserror 2.0.18", + "thiserror", "time", "tokio", "tokio-util", @@ -2956,7 +2942,7 @@ dependencies = [ "indexmap", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -3073,9 +3059,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3163,7 +3149,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -3185,7 +3171,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -3223,6 +3209,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -3248,7 +3244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3280,7 +3276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04082e93ed1a06debd9148c928234b46d2cf260bc65f44e1d1d3fa594c5beebc" dependencies = [ "simdutf8", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -3326,33 +3322,13 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3417,9 +3393,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3442,9 +3418,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3509,20 +3485,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http 1.4.0", "http-body 1.0.1", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -3627,9 +3603,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" @@ -3742,11 +3718,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -3755,14 +3731,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -3773,9 +3749,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3783,9 +3759,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3793,9 +3769,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -3806,18 +3782,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.67" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941c102b3f0c15b6d72a53205e09e6646aafcf2991e18412cc331dbac1806bc0" +checksum = "af5ec93229ad9ccd0a545a516dec76dc276613f278f6a91aa6b463d5b33d42d0" dependencies = [ "async-trait", "cast", @@ -3837,9 +3813,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.67" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26bd6570f39bb1440fd8f01b63461faaf2a3f6078a508e4e54efa99363108d2" +checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb" dependencies = [ "proc-macro2", "quote", @@ -3848,9 +3824,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.117" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c29582b14d5bf030b02fa232b9b57faf2afc322d2c61964dd80bad02bf76207" +checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527" [[package]] name = "wasm-encoder" @@ -3901,9 +3877,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -3921,9 +3897,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -3934,7 +3910,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3996,15 +3972,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -4032,21 +3999,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4080,12 +4032,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4098,12 +4044,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4116,12 +4056,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4146,12 +4080,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4164,12 +4092,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4182,12 +4104,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4200,12 +4116,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4227,6 +4137,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -4308,9 +4224,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xmlparser" @@ -4320,9 +4236,9 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4331,9 +4247,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4343,18 +4259,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4363,18 +4279,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4390,9 +4306,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4401,9 +4317,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4412,9 +4328,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e87463a3..b4502703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,11 +57,11 @@ path-absolutize = "3.1.1" std-next = "0.1.9" # Crypto -crc-fast = "1.9.0" +crc-fast = "1.10.0" crc32c = "0.6.8" hmac = "0.13.0" md-5 = "0.11.0" -openssl = "0.10.79" +openssl = "0.10.80" sha1 = "0.11.0" sha2 = "0.11.0" subtle = "2.6.1" @@ -83,7 +83,7 @@ futures = { version = "0.3.32", default-features = false } futures-util = "0.3.32" pin-project-lite = "0.2.17" sync_wrapper = { version = "1.0.2", default-features = false } -tokio = "1.52.1" +tokio = "1.52.3" tokio-util = "0.7.18" transform-stream = "0.3.1" @@ -114,10 +114,10 @@ clap = { version = "4.6.1", features = ["derive"] } opendal = { version = "0.56.0", default-features = false } # AWS SDK -aws-config = { version = "1.8.15", default-features = false } +aws-config = { version = "1.8.16", default-features = false } aws-credential-types = "1.2.14" -aws-sdk-s3 = { version = "1.129.0", default-features = false, features = ["default-https-client", "http-1x", "rt-tokio", "sigv4a"] } -aws-sdk-sts = { version = "1.102.0", default-features = false, features = ["default-https-client", "rt-tokio"] } -aws-smithy-runtime-api = "1.12.0" -aws-smithy-types = "1.4.7" +aws-sdk-s3 = { version = "1.132.0", default-features = false, features = ["default-https-client", "http-1x", "rt-tokio", "sigv4a"] } +aws-sdk-sts = { version = "1.103.0", default-features = false, features = ["default-https-client", "rt-tokio"] } +aws-smithy-runtime-api = "1.12.1" +aws-smithy-types = "1.4.8" aws-smithy-types-convert = "0.60.14" diff --git a/crates/s3s-wasm/Cargo.toml b/crates/s3s-wasm/Cargo.toml index 7058fd7d..5705474d 100644 --- a/crates/s3s-wasm/Cargo.toml +++ b/crates/s3s-wasm/Cargo.toml @@ -17,4 +17,4 @@ s3s = { version = "0.14.0-dev", path = "../s3s", default-features = false } workspace = true [dev-dependencies] -wasm-bindgen-test = "0.3.67" +wasm-bindgen-test = "0.3.71" From 05bba651be924bc25792c0fb4c3b267065d377e3 Mon Sep 17 00:00:00 2001 From: houseme Date: Wed, 20 May 2026 19:23:38 +0800 Subject: [PATCH 19/20] style: format presigned canonical request calls --- crates/s3s/src/ops/signature.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/s3s/src/ops/signature.rs b/crates/s3s/src/ops/signature.rs index baed1560..401a8451 100644 --- a/crates/s3s/src/ops/signature.rs +++ b/crates/s3s/src/ops/signature.rs @@ -376,9 +376,16 @@ impl SignatureContext<'_> { region, service, }; - let canonical_request = sig_v4::create_presigned_canonical_request(method, self.decoded_uri_path, qs.as_ref(), &headers, payload); + let canonical_request = + sig_v4::create_presigned_canonical_request(method, self.decoded_uri_path, qs.as_ref(), &headers, payload); verifier.verify_with_raw_path_fallback(&canonical_request, || { - sig_v4::create_presigned_canonical_request_with_raw_uri_path(method, self.raw_uri_path, qs.as_ref(), &headers, payload) + sig_v4::create_presigned_canonical_request_with_raw_uri_path( + method, + self.raw_uri_path, + qs.as_ref(), + &headers, + payload, + ) })?; if let Some(content_sha256) = self.hs.get_unique(crate::header::X_AMZ_CONTENT_SHA256) From 83707bcf408aaa73166e546f53b5ac8ef42efd41 Mon Sep 17 00:00:00 2001 From: houseme Date: Wed, 20 May 2026 19:31:05 +0800 Subject: [PATCH 20/20] test: remove unused auth import in signature tests --- crates/s3s/src/ops/signature.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/s3s/src/ops/signature.rs b/crates/s3s/src/ops/signature.rs index 401a8451..97c72068 100644 --- a/crates/s3s/src/ops/signature.rs +++ b/crates/s3s/src/ops/signature.rs @@ -708,7 +708,7 @@ impl SignatureContext<'_> { mod tests { use super::*; use crate::S3ErrorCode; - use crate::auth::{SecretKey, SimpleAuth}; + use crate::auth::SecretKey; use crate::config::{S3ConfigProvider, StaticConfigProvider}; use crate::sig_v4; use crate::sig_v4::AmzDate;