labels.rs (9038B)
1 // Copyright 2022 Adobe. All rights reserved. 2 // This file is licensed to you under the Apache License, 3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 // or the MIT license (http://opensource.org/licenses/MIT), 5 // at your option. 6 7 // Unless required by applicable law or agreed to in writing, 8 // this software is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 // specific language governing permissions and limitations under 12 // each license. 13 14 #![deny(missing_docs)] 15 16 //! Labels for JUMBF boxes as defined in C2PA 1.0 Specification. 17 //! 18 //! See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>. 19 20 /// Label for the C2PA manifest store. 21 /// 22 /// This value should be used when possible, since it may contain a version suffix 23 /// when needed to support a future version of the spec. 24 /// 25 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>. 26 pub const MANIFEST_STORE: &str = "c2pa"; 27 28 /// Label for the C2PA assertion store box. 29 /// 30 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>. 31 pub const ASSERTIONS: &str = "c2pa.assertions"; 32 33 /// Label for the C2PA claim box. 34 /// 35 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>. 36 pub const CLAIM: &str = "c2pa.claim"; 37 38 /// Label for the C2PA claim signature box. 39 /// 40 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_c2pa_box_details>. 41 pub const SIGNATURE: &str = "c2pa.signature"; 42 43 /// Label for the credentials store box. 44 /// 45 /// See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_credential_storage>. 46 pub const CREDENTIALS: &str = "c2pa.credentials"; 47 48 /// Label for the DataBox box. 49 /// 50 /// See <https://c2pa.org/specifications/specifications/1.3/specs/C2PA_Specification.html#_data_boxes>. 51 pub const DATABOX: &str = "c2pa.data"; 52 53 /// Label for the DataBox store box. 54 /// 55 /// See <https://c2pa.org/specifications/specifications/1.3/specs/C2PA_Specification.html#_data_storage>. 56 pub const DATABOXES: &str = "c2pa.databoxes"; 57 58 const JUMBF_PREFIX: &str = "self#jumbf"; 59 60 // Converts a manifest label to a JUMBF URI. 61 pub(crate) fn to_manifest_uri(manifest_label: &str) -> String { 62 format!("{JUMBF_PREFIX}=/{MANIFEST_STORE}/{manifest_label}") 63 } 64 65 // Converts a manifest label and an assertion label into a JUMBF URI. 66 pub(crate) fn to_assertion_uri(manifest_label: &str, assertion_label: &str) -> String { 67 format!( 68 "{}/{}/{}", 69 to_manifest_uri(manifest_label), 70 ASSERTIONS, 71 assertion_label 72 ) 73 } 74 75 // Converts a manifest label to a JUMBF URI for its signature. 76 pub(crate) fn to_signature_uri(manifest_label: &str) -> String { 77 format!("{}/{}", to_manifest_uri(manifest_label), SIGNATURE) 78 } 79 80 // Converts a manifest label and an assertion label to a JUMBF 81 // verifiable credential URL. 82 pub(crate) fn to_verifiable_credential_uri(manifest_label: &str, vc_id: &str) -> String { 83 // TO CONSIDER: Does this now belong in jumbf::labels? 84 format!( 85 "{}/{}/{}", 86 to_manifest_uri(manifest_label), 87 CREDENTIALS, 88 vc_id 89 ) 90 } 91 92 // Converts a manifest label and a DataBox label to a JUMBF 93 // HashedURI. 94 pub(crate) fn to_databox_uri(manifest_label: &str, databox_id: &str) -> String { 95 // TO CONSIDER: Does this now belong in jumbf::labels? 96 format!( 97 "{}/{}/{}", 98 to_manifest_uri(manifest_label), 99 DATABOXES, 100 databox_id 101 ) 102 } 103 104 // Split off JUMBF prefix. 105 pub(crate) fn to_normalized_uri(uri: &str) -> String { 106 let uri_parts: Vec<&str> = uri.split('=').collect(); 107 108 let output = if uri_parts.len() == 1 { 109 uri_parts[0].to_string() 110 } else { 111 uri_parts[1].to_string() 112 }; 113 114 // Add leading "/" if needed. 115 let mut manifest_store_part = MANIFEST_STORE.to_string(); 116 manifest_store_part.push('/'); 117 118 if !output.is_empty() && output.starts_with(&manifest_store_part) { 119 format!("{}{}", "/", output) 120 } else { 121 output 122 } 123 } 124 125 // Converts a possibly relative JUMBF URI to an absolute URI to the manifest store. 126 pub(crate) fn to_absolute_uri(manifest_label: &str, uri: &str) -> String { 127 let raw_uri = to_normalized_uri(uri); 128 let parts: Vec<&str> = raw_uri.split('/').collect(); 129 if parts.len() > 2 && parts[1] == MANIFEST_STORE { 130 uri.to_string() 131 } else { 132 format!("{}/{}", to_manifest_uri(manifest_label), raw_uri) 133 } 134 } 135 136 // Converts an absolute JUMBF URI to a URI relative to the manifest store. 137 pub(crate) fn to_relative_uri(uri: &str) -> String { 138 let raw_uri = to_normalized_uri(uri); 139 let parts: Vec<&str> = raw_uri.split('/').collect(); 140 141 if parts.len() > 4 && parts[1] == MANIFEST_STORE { 142 format!("{}={}", JUMBF_PREFIX, parts[3..].join("/")) 143 } else { 144 // Doesn't look like an absolute URI, so we'll return it as-is. 145 uri.to_string() 146 } 147 } 148 149 // Given a JUMBF URI, return the manifest label contained within it. 150 pub(crate) fn manifest_label_from_uri(uri: &str) -> Option<String> { 151 let raw_uri = to_normalized_uri(uri); 152 let parts: Vec<&str> = raw_uri.split('/').collect(); 153 if parts.len() > 2 && parts[1] == MANIFEST_STORE { 154 Some(parts[2].to_string()) 155 } else { 156 None 157 } 158 } 159 160 // Extract an assertion label from a JUMBF URI. 161 pub(crate) fn assertion_label_from_uri(uri: &str) -> Option<String> { 162 let raw_uri = to_normalized_uri(uri); 163 let parts: Vec<&str> = raw_uri.split('/').collect(); 164 if parts.len() > 4 && parts[1] == MANIFEST_STORE && parts[3] == ASSERTIONS { 165 Some(parts[4].to_string()) 166 } else if parts[0] == ASSERTIONS { 167 Some(parts[1].to_string()) 168 } else { 169 None 170 } 171 } 172 173 // Extract the box the label points to. 174 pub(crate) fn box_name_from_uri(uri: &str) -> Option<String> { 175 let raw_uri = to_normalized_uri(uri); 176 let parts: Vec<&str> = raw_uri.split('/').collect(); 177 178 parts.last().map(|b| b.to_string()) 179 } 180 181 #[cfg(test)] 182 pub mod tests { 183 #![allow(clippy::unwrap_used)] 184 185 use super::*; 186 187 #[test] 188 fn test_manifest_uri() { 189 assert_eq!( 190 to_manifest_uri("acme::urn:uuid::123:456:789"), 191 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789" 192 ); 193 } 194 195 #[test] 196 fn test_assertion_uri() { 197 assert_eq!( 198 to_assertion_uri("acme::urn:uuid::123:456:789", "c2pa.thumbnail.claim.jpeg"), 199 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.assertions/c2pa.thumbnail.claim.jpeg" 200 ); 201 } 202 203 #[test] 204 fn test_signature_uri() { 205 assert_eq!( 206 to_signature_uri("acme::urn:uuid::123:456:789"), 207 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.signature" 208 ); 209 } 210 211 #[test] 212 fn test_verifiable_credential_uri() { 213 assert_eq!( 214 to_verifiable_credential_uri("acme::urn:uuid::123:456:789", "12315142234@acme.com"), 215 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.credentials/12315142234@acme.com" 216 ); 217 } 218 219 #[test] 220 fn test_relative_uri() { 221 assert_eq!( 222 to_relative_uri( 223 "self#jumbf=/c2pa/acme::urn:uuid::123:456:789/c2pa.assertions/c2pa.thumbnail.claim.jpeg" 224 ), 225 "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg" 226 ); 227 } 228 229 #[test] 230 fn test_paths() { 231 let manifest = "acme::urn:uuid::123:456:789"; 232 let assertion = "c2pa.thumbnail.claim.jpeg"; 233 let empty_uri = ""; 234 let absolute_uri = to_manifest_uri(manifest); 235 236 let raw_uri = to_normalized_uri(&absolute_uri); 237 238 let raw_uri_no_slash = 239 to_normalized_uri(&format!("{JUMBF_PREFIX}={MANIFEST_STORE}/{manifest}")); 240 241 let raw_empty_uri = to_normalized_uri(empty_uri); 242 243 assert_eq!(raw_uri, raw_uri_no_slash); 244 assert_eq!(raw_empty_uri, ""); 245 246 let manifest_label_from_absolute = manifest_label_from_uri(&absolute_uri); 247 let manifest_label_from_nomalized = manifest_label_from_uri(&raw_uri); 248 249 assert_eq!(manifest_label_from_absolute, manifest_label_from_nomalized); 250 251 let assertion_uri = to_assertion_uri(manifest, assertion); 252 253 assert_eq!( 254 Some(manifest.to_string()), 255 manifest_label_from_uri(&assertion_uri) 256 ); 257 assert_eq!( 258 Some(assertion.to_string()), 259 assertion_label_from_uri(&assertion_uri) 260 ); 261 assert_eq!(None, assertion_label_from_uri(&absolute_uri)); 262 263 let assertion_relative = to_relative_uri(&assertion_uri); 264 265 assert_eq!( 266 assertion_relative, 267 format!("{JUMBF_PREFIX}={ASSERTIONS}/{assertion}") 268 ); 269 assert_eq!( 270 Some(assertion.to_string()), 271 assertion_label_from_uri(&assertion_relative) 272 ); 273 } 274 }