Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.1"
".": "0.2.0"
}
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Changelog

## [0.2.0](https://github.com/developmentseed/multistore/compare/multistore-v0.1.1...multistore-v0.2.0) (2026-03-19)


### Features

* add display_name field to ResolvedBucket ([263d324](https://github.com/developmentseed/multistore/commit/263d324fe417a9bb9a4d04fcac9b029688d7be19))
* add metering middleware ([20395f7](https://github.com/developmentseed/multistore/commit/20395f74def521b4ee74260e8cc8e399e81be25c))
* add metering middleware ([73c2d2c](https://github.com/developmentseed/multistore/commit/73c2d2cdc9dd28606b2da0e4217e222881c05600))
* add OidcDiscoveryRouteHandler for .well-known endpoints ([d5f01a0](https://github.com/developmentseed/multistore/commit/d5f01a0e9b52ce92e9c309f03b48c99adc3ef11a))
* add RouteHandler trait and RequestInfo for pluggable pre-dispatch routing ([1db06a8](https://github.com/developmentseed/multistore/commit/1db06a873b4af645082899520c14439d5b419202))
* add StsRouteHandler for AssumeRoleWithWebIdentity interception ([c6d6d66](https://github.com/developmentseed/multistore/commit/c6d6d66fb908d8471f0e607b1f8ded10b916f30e))
* **cf-workers:** add azure/gcp feature flags for StoreBuilder variants ([61aa2f5](https://github.com/developmentseed/multistore/commit/61aa2f53e4ba6c9ec28c061ed7d1ec7b86e6e6a2))
* **core:** support percent encoding ([8c69f11](https://github.com/developmentseed/multistore/commit/8c69f118ac71bf088e305d9c9e5db825dc303dcc))
* create multistore-cf-workers crate with reusable Workers adapters ([73c79b5](https://github.com/developmentseed/multistore/commit/73c79b582d88334fb7a65970905aaa6bda4b3f57))
* create multistore-path-mapping crate for hierarchical path routing ([19d6626](https://github.com/developmentseed/multistore/commit/19d66268b8d038eae403f51ebb9ca869f4d194ae))
* get-object ([#4](https://github.com/developmentseed/multistore/issues/4)) ([9f3d8e8](https://github.com/developmentseed/multistore/commit/9f3d8e85ce97a265907e4473401b94829258c992))
* support pagination on bucket list ([017f22d](https://github.com/developmentseed/multistore/commit/017f22d531d6a54e90f2d560fe46b2b1aebce6ea))
* support range requests ([4053c5f](https://github.com/developmentseed/multistore/commit/4053c5f8ae6cf5089c863018ac575ba2caba8e25))
* **workers:** add rate-limiting ([47a5973](https://github.com/developmentseed/multistore/commit/47a5973856f8a4c46167951ce439fe2abf65be59))


### Bug Fixes

* add default allowed roles ([35dd823](https://github.com/developmentseed/multistore/commit/35dd82352e7f14fb8f87c71e98862f737cb96a07))
* apply ListRewrite.add_prefix to Prefix element and fix double-slash in rewrite_key ([3ba9890](https://github.com/developmentseed/multistore/commit/3ba98907a0eea206741afe85c4c042281630709e))
* **ci:** add --cwd to wrangler commands and fix step reference ([a250cf2](https://github.com/developmentseed/multistore/commit/a250cf2efd0ae0d32a36132c935fec9b1fcce5bf))
* **ci:** let wrangler output stream directly ([f98780e](https://github.com/developmentseed/multistore/commit/f98780e55380086dae3e2e3ec83bfa34187a0c11))
* **ci:** pass CLOUDFLARE_ACCOUNT_ID as secret to reusable workflow ([c2ff2a7](https://github.com/developmentseed/multistore/commit/c2ff2a7b70b304e4b99c3b41aa3b45d0332a12bc))
* correct endpoint ([0d9fd27](https://github.com/developmentseed/multistore/commit/0d9fd27a9f3e55055a1a3723f55a528e9e974cce))
* correct STS endpoint ([3f6b2be](https://github.com/developmentseed/multistore/commit/3f6b2be6f868957fd6d2b19536ae43a2aae6a990))
* ensure cloudflare streams data properly ([19053b2](https://github.com/developmentseed/multistore/commit/19053b29ecc41896f05bd062713f36927289d57d))
* handle Azure and GCS URLs in UnsignedUrlSigner ([3af3845](https://github.com/developmentseed/multistore/commit/3af3845c900843e073dca031a8f15bbe692a1089))
* match S3 ListObjectsV2 delimiter behavior ([ad0acfb](https://github.com/developmentseed/multistore/commit/ad0acfbe953f1ca9dc57c9f2a5b53844143e969f))
* pin worker version ([2752efb](https://github.com/developmentseed/multistore/commit/2752efb7e6cea5b74a6f399e4e025b525f4124dd))
* **rate-limit:** ensure middleware runs before bucket resolution ([b15cd64](https://github.com/developmentseed/multistore/commit/b15cd64664e195c6ed39e3cc8673c62dcd7d4f25))
* support range HEAD requests ([758c44c](https://github.com/developmentseed/multistore/commit/758c44c13cc59a9f9ee6381b3d6b0573fa084c55))
* **workers:** support multipart downloads ([7e4c313](https://github.com/developmentseed/multistore/commit/7e4c313e693d02c4b9adfaa36c82f3851504c41c))
* **workers:** us sqlite durable object storage ([65b7101](https://github.com/developmentseed/multistore/commit/65b71017691d868037815ca8f284bddc6e1c80d8))


### Performance Improvements

* use Cow<BucketConfig> in OidcBackendAuth to avoid per-request clones ([4dff450](https://github.com/developmentseed/multistore/commit/4dff4509a87524ce5eb3d3154de6dfa8c39b9256))
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ default-members = [
resolver = "2"

[workspace.package]
version = "0.1.1"
version = "0.2.0"
edition = "2021"
license = "MIT"

Expand Down Expand Up @@ -100,10 +100,10 @@ console_error_panic_hook = "0.1.7"
lambda_http = "0.13"

# Internal crates
multistore = { path = "crates/core", version = "0.1.1" }
multistore-static-config = { path = "crates/static-config", version = "0.1.1" }
multistore-sts = { path = "crates/sts", version = "0.1.1" }
multistore-metering = { path = "crates/metering", version = "0.1.1" }
multistore-cf-workers = { path = "crates/cf-workers", version = "0.1.1" }
multistore-oidc-provider = { path = "crates/oidc-provider", version = "0.1.1" }
multistore-path-mapping = { path = "crates/path-mapping", version = "0.1.1" }
multistore = { path = "crates/core", version = "0.2.0" }
multistore-static-config = { path = "crates/static-config", version = "0.2.0" }
multistore-sts = { path = "crates/sts", version = "0.2.0" }
multistore-metering = { path = "crates/metering", version = "0.2.0" }
multistore-cf-workers = { path = "crates/cf-workers", version = "0.2.0" }
multistore-oidc-provider = { path = "crates/oidc-provider", version = "0.2.0" }
multistore-path-mapping = { path = "crates/path-mapping", version = "0.2.0" }
64 changes: 47 additions & 17 deletions crates/core/src/api/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,12 @@ pub(crate) fn build_list_xml(
if url_encode {
// S3 URL-encodes per RFC 3986: leave unreserved chars + '/' unencoded.
// Unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
const S3_ENCODE_SET: &percent_encoding::AsciiSet =
&percent_encoding::NON_ALPHANUMERIC
.remove(b'-')
.remove(b'.')
.remove(b'_')
.remove(b'~')
.remove(b'/');
const S3_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
.remove(b'-')
.remove(b'.')
.remove(b'_')
.remove(b'~')
.remove(b'/');
percent_encoding::utf8_percent_encode(&s, S3_ENCODE_SET).to_string()
} else {
s
Expand Down Expand Up @@ -375,16 +374,36 @@ mod tests {
let xml = build_list_xml(&params, &list_result, &config, None).unwrap();

// EncodingType element should be present
assert!(xml.contains("<EncodingType>url</EncodingType>"), "Missing EncodingType element: {}", xml);
assert!(
xml.contains("<EncodingType>url</EncodingType>"),
"Missing EncodingType element: {}",
xml
);
// Key: spaces encoded, but '/', '.', '-' preserved (RFC 3986 unreserved + '/')
assert!(xml.contains("<Key>dir/file%20with%20spaces.txt</Key>"), "Key not encoded correctly: {}", xml);
assert!(
xml.contains("<Key>dir/file%20with%20spaces.txt</Key>"),
"Key not encoded correctly: {}",
xml
);
// CommonPrefix: spaces encoded, '/' preserved
assert!(xml.contains("<Prefix>dir/sub%20dir/</Prefix>"), "CommonPrefix not encoded correctly: {}", xml);
assert!(
xml.contains("<Prefix>dir/sub%20dir/</Prefix>"),
"CommonPrefix not encoded correctly: {}",
xml
);
// Prefix: '/' preserved
assert!(xml.contains("<Prefix>dir/</Prefix>") || xml.contains("<Prefix>dir/sub%20dir/</Prefix>"),
"Prefix not encoded correctly: {}", xml);
assert!(
xml.contains("<Prefix>dir/</Prefix>")
|| xml.contains("<Prefix>dir/sub%20dir/</Prefix>"),
"Prefix not encoded correctly: {}",
xml
);
// Delimiter: '/' preserved
assert!(xml.contains("<Delimiter>/</Delimiter>"), "Delimiter should not encode '/': {}", xml);
assert!(
xml.contains("<Delimiter>/</Delimiter>"),
"Delimiter should not encode '/': {}",
xml
);
}

#[test]
Expand All @@ -409,8 +428,11 @@ mod tests {

let xml = build_list_xml(&params, &list_result, &config, None).unwrap();

assert!(xml.contains("<Key>test_file%283%29.png</Key>"),
"Expected S3-style encoding of parens: {}", xml);
assert!(
xml.contains("<Key>test_file%283%29.png</Key>"),
"Expected S3-style encoding of parens: {}",
xml
);
}

#[test]
Expand All @@ -434,9 +456,17 @@ mod tests {
let xml = build_list_xml(&params, &list_result, &config, None).unwrap();

// No EncodingType element
assert!(!xml.contains("<EncodingType>"), "EncodingType should not be present: {}", xml);
assert!(
!xml.contains("<EncodingType>"),
"EncodingType should not be present: {}",
xml
);
// Key should NOT be URL-encoded (spaces are XML-safe)
assert!(xml.contains("<Key>dir/file with spaces.txt</Key>"), "Key should be raw: {}", xml);
assert!(
xml.contains("<Key>dir/file with spaces.txt</Key>"),
"Key should be raw: {}",
xml
);
}

#[test]
Expand Down
Loading