commit 041d7ba7b732ed8f8c1da869172297771435d5ff
parent bb54297d208f8e3817485d1c1a63a386f457349a
Author: triesap <tyson@radroots.org>
Date: Sun, 13 Sep 2026 04:04:29 +0000
blossom: project the upload URL from a canonical blob reference
- Keep upload destinations separate from blob retrieval references
- Preserve exact origin hash and reference approval without I/O
- Cover endpoint variants across default and minimal features
- Verify the additive API and unchanged coverage and release gates
Diffstat:
4 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/contracts/api_baselines/radroots_blossom.txt b/contracts/api_baselines/radroots_blossom.txt
@@ -212,6 +212,7 @@ pub fn radroots_blossom::url::BlobUrl::is_loopback_http(&self) -> bool
pub fn radroots_blossom::url::BlobUrl::parse(&str) -> core::result::Result<Self, radroots_blossom::Error>
pub fn radroots_blossom::url::BlobUrl::port(&self) -> core::option::Option<u16>
pub fn radroots_blossom::url::BlobUrl::scheme(&self) -> &str
+pub fn radroots_blossom::url::BlobUrl::upload_url(&self) -> alloc::string::String
impl core::fmt::Display for radroots_blossom::url::BlobUrl
pub fn radroots_blossom::url::BlobUrl::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::str::traits::FromStr for radroots_blossom::url::BlobUrl
@@ -300,6 +301,7 @@ pub fn radroots_blossom::url::BlobUrl::is_loopback_http(&self) -> bool
pub fn radroots_blossom::url::BlobUrl::parse(&str) -> core::result::Result<Self, radroots_blossom::Error>
pub fn radroots_blossom::url::BlobUrl::port(&self) -> core::option::Option<u16>
pub fn radroots_blossom::url::BlobUrl::scheme(&self) -> &str
+pub fn radroots_blossom::url::BlobUrl::upload_url(&self) -> alloc::string::String
impl core::fmt::Display for radroots_blossom::url::BlobUrl
pub fn radroots_blossom::url::BlobUrl::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::str::traits::FromStr for radroots_blossom::url::BlobUrl
diff --git a/crates/blossom/README.md b/crates/blossom/README.md
@@ -99,6 +99,11 @@ display, cache, or otherwise act on an unapproved URL. URL approval is a narrow
transport policy, not host reputation, content safety, malware scanning, or
application media policy.
+`BlobUrl::upload_url` projects the BUD-02 `/upload` URL at the same scheme,
+host and port. It preserves the canonical blob reference used for retrieval
+verification and grants no transport authority. Upload callers must still
+enforce their configured endpoint policy and bind authorization to exact bytes.
+
`ByteVerifiedDescriptor` can only be produced after an approved descriptor's
hash, byte length, and approved media type match supplied bytes or a locally
computed `ByteCommitment`. It proves local descriptor-to-byte agreement. It is
diff --git a/crates/blossom/src/url.rs b/crates/blossom/src/url.rs
@@ -6,7 +6,6 @@
//! the reference for transport; it does not perform a request or establish host
//! reputation, byte integrity, authenticity, or application media safety.
-#[cfg(feature = "serde")]
use alloc::string::String;
use alloc::string::ToString;
use core::{fmt, str::FromStr};
@@ -55,6 +54,17 @@ impl BlobUrl {
self.url.as_str()
}
+ /// Returns the BUD-02 upload URL at this blob's exact origin.
+ ///
+ /// This is a structural projection, not transport authorization. Callers
+ /// must still enforce endpoint policy and bind the upload to exact bytes.
+ /// The canonical blob reference remains unchanged for retrieval verification.
+ pub fn upload_url(&self) -> String {
+ let mut url = self.url.clone();
+ url.set_path("/upload");
+ url.to_string()
+ }
+
pub fn scheme(&self) -> &str {
self.url.scheme()
}
diff --git a/crates/blossom/tests/parser_properties.rs b/crates/blossom/tests/parser_properties.rs
@@ -10,6 +10,38 @@ const ASCII_MUTATION_ALPHABET: &[u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._/:?#%\\ @\t\n\r\0;=+";
#[test]
+fn upload_url_preserves_origin_without_replacing_or_approving_the_blob() {
+ let hash = Sha256::digest(b"bound upload bytes");
+ for (origin, expected) in [
+ ("https://media.example", "https://media.example/upload"),
+ ("https://media.example:443", "https://media.example/upload"),
+ (
+ "https://media.example:8443",
+ "https://media.example:8443/upload",
+ ),
+ ("http://127.0.0.1:21100", "http://127.0.0.1:21100/upload"),
+ ("http://[::1]:21100", "http://[::1]:21100/upload"),
+ (
+ "https://[2001:db8::1]:8443",
+ "https://[2001:db8::1]:8443/upload",
+ ),
+ ("http://insecure.example", "http://insecure.example/upload"),
+ ] {
+ for extension in ["", ".png", ".jpeg"] {
+ let blob = BlobUrl::parse(&format!("{origin}/{hash}{extension}")).unwrap();
+ let original = blob.to_string();
+ let approved_before = blob.clone().approve();
+ assert_eq!(blob.upload_url(), expected);
+ assert_eq!(blob.upload_url(), expected);
+ assert_eq!(blob.to_string(), original);
+ assert_eq!(blob.hash_path().hash(), hash);
+ assert_eq!(blob.clone().approve(), approved_before);
+ assert!(BlobUrl::parse(&blob.upload_url()).is_err());
+ }
+ }
+}
+
+#[test]
fn deterministic_parser_mutation_corpus_never_panics_and_round_trips_successes() {
let fixed = [
"",