record.rs (13042B)
1 use std::str::FromStr; 2 //use std::io; 3 use std::convert::Infallible; 4 use std::fs::{ 5 File, 6 remove_file, 7 }; 8 use std::io::{ 9 Write, 10 Read, 11 }; 12 use std::os::unix::fs::symlink; 13 use std::path::{ 14 PathBuf, 15 Path, 16 }; 17 use std::fs::copy as fs_copy; 18 use std::error::Error; 19 use sha2::{Sha256, Digest}; 20 use std::fmt; 21 22 use crate::auth::AuthResult; 23 //use tiny_http::Request; 24 use tempfile::NamedTempFile; 25 26 use mime::Mime; 27 28 use log::{debug, info, error}; 29 30 #[derive(Debug, PartialEq)] 31 /// Status codes to represent the result of a request. 32 pub enum RequestResultType { 33 /// Record has been found. 34 Found, 35 /// Record has been updated or created. 36 Changed, 37 /// Cannot find and/or read record. 38 ReadError, 39 /// Cannot write immutable record to storage. 40 WriteError, 41 /// Authentication cannot be verified (signature mismatch). 42 AuthError, 43 /// Invalid request from client. 44 InputError, 45 /// Cannot store mutable record. 46 RecordError, 47 } 48 49 /// Interface to interpret and read the result of a request. 50 pub struct RequestResult { 51 /// Result code of the request. 52 pub typ: RequestResultType, 53 /// Contains the length of the body of a GET or HEAD request. 54 pub l: Option<u64>, 55 /// Contains the result body (reference string) of a PUT request. 56 pub v: Option<String>, 57 /// Contains the result body (as a reader) of a GET request. 58 pub f: Option<File>, 59 /// Contains the MIME type of the content of a GET response (if build with the `meta` feature). 60 pub m: Option<Mime>, 61 /// Contains the file name to use for download request requesting a filename. 62 pub n: Option<String>, 63 /// Contains the authentication result. 64 pub a: Option<AuthResult>, 65 /// Aliase content, in case of mutable reference. 66 pub s: Option<String>, 67 } 68 69 impl RequestResult { 70 pub fn new(typ: RequestResultType) -> RequestResult { 71 RequestResult { 72 typ: typ, 73 v: None, 74 l: None, 75 f: None, 76 m: None, 77 n: None, 78 a: None, 79 s: None, 80 } 81 } 82 83 pub fn with_content(mut self, s: String) -> RequestResult { 84 self.v = Some(s); 85 self 86 } 87 88 pub fn with_auth(mut self, a: AuthResult) -> RequestResult { 89 self.a = Some(a); 90 self 91 } 92 93 pub fn with_file(mut self, f: File) -> RequestResult { 94 self.f = Some(f); 95 self 96 } 97 98 pub fn with_aliased(mut self, s: String) -> RequestResult { 99 self.s = Some(s); 100 self 101 } 102 103 pub fn with_length(mut self, l: u64) -> RequestResult { 104 self.l = Some(l); 105 self 106 } 107 } 108 109 impl fmt::Display for RequestResult { 110 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 111 fmt.write_str(&self.to_string()) 112 } 113 } 114 115 impl fmt::Debug for RequestResult { 116 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 117 //fmt.write_str(format_args!("{:?}", RequestResultType)); 118 write!(fmt, "{:?}", self.typ) 119 } 120 } 121 122 impl Error for RequestResult { 123 fn description(&self) -> &str { 124 match &self.v { 125 Some(v) => { 126 return v.as_str(); 127 }, 128 None => { 129 }, 130 } 131 "" 132 } 133 } 134 135 /// Represents a single record on the server. 136 pub struct Record { 137 /// Digest of content. 138 pub digest: Vec<u8>, 139 /// Server side path to content. 140 pub path: PathBuf, 141 /// Alias 142 pub alias: Option<Vec<u8>>, 143 } 144 145 /// Identifier part of the for mutable content reference. 146 pub struct ResourceKey { 147 pub v: Vec<u8>, 148 } 149 150 impl FromStr for ResourceKey { 151 type Err = Infallible; 152 153 fn from_str(s: &str) -> Result<ResourceKey, Infallible> { 154 let mut h = Sha256::new(); 155 h.update(&s[..]); 156 let k = ResourceKey{ 157 v: h.finalize().to_vec(), 158 }; 159 Ok(k) 160 } 161 } 162 163 impl fmt::Display for ResourceKey { 164 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 165 fmt.write_str(&hex::encode(&self.v)) 166 } 167 } 168 169 impl ResourceKey { 170 /// Create a reference for the identifier using the verified identity from client 171 /// authentication. 172 pub fn pointer_for(&self, subject: &AuthResult) -> Vec<u8> { 173 let mut h = Sha256::new(); 174 debug!("update {:?} {:?}", hex::encode(&self.v), hex::encode(&subject.identity)); 175 h.update(&self.v); 176 h.update(&subject.identity); 177 h.finalize().to_vec() 178 } 179 } 180 181 182 /// Store an immutable record on file. 183 /// 184 /// # Arguments 185 /// 186 /// * `path` - Absolute path to storage directory. 187 /// * `f` - Reader providing the contents of the file. 188 /// * `expected_size` - Size hint of content. 189 /// TODO: handle write fail 190 pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Result<Record, RequestResult> { 191 let z: Vec<u8>; 192 let hash: String; 193 let tempfile = match NamedTempFile::new() { 194 Ok(of) => { 195 let mut total_size: usize = 0; 196 debug!("writing to tempfile {:?} expected size {}", of.path(), expected_size); 197 let mut buf: [u8; 65535] = [0; 65535]; 198 let mut h = Sha256::new(); 199 loop { 200 match f.read(&mut buf[..]) { 201 Ok(v) => { 202 if v == 0 { 203 break; 204 } 205 total_size += v; 206 let data = &buf[..v]; 207 h.update(data); 208 _ = of.as_file().write(data); 209 }, 210 Err(e) => { 211 error!("cannot read from request body: {}", e); 212 let err = RequestResult::new(RequestResultType::ReadError); 213 return Err(err); 214 }, 215 } 216 } 217 218 if expected_size > 0 { 219 if expected_size != total_size { 220 let err = RequestResult::new(RequestResultType::ReadError); 221 return Err(err); 222 } 223 } 224 225 z = h.finalize().to_vec(); 226 hash = hex::encode(&z); 227 info!("have hash {} for content, length {}", hash, total_size); 228 of 229 }, 230 Err(_) => { 231 let err = RequestResult::new(RequestResultType::WriteError); 232 return Err(err); 233 } 234 }; 235 236 let final_path_buf = path.join(&hash); 237 let final_path = final_path_buf.as_path(); 238 match fs_copy(tempfile.path(), &final_path) { 239 Ok(_) => { 240 }, 241 Err(e) => { 242 error!("could not store immutable: {:?} ({})", final_path, e); 243 }, 244 } 245 246 let rec = Record{ 247 digest: z, 248 path: final_path_buf, 249 alias: None, 250 }; 251 Ok(rec) 252 } 253 254 /// Store an immutable record on file with a mutable reference. 255 /// 256 /// This method will fail if the provided [auth::AuthResult](crate::auth::AuthResult) is not a 257 /// successful authentcation. 258 /// 259 /// # Arguments 260 /// 261 /// * `path` - Absolute path to storage directory. 262 /// * `f` - Reader providing the contents of the file. 263 /// * `expected_size` - Size hint of content. 264 /// * `key` - Mutable reference generator. 265 /// * `auth` - Authentication result containing the client identity. 266 //pub fn put_mutable(path: &Path, mut f: impl Read, expected_size: usize, key: &ResourceKey, auth: &AuthResult) -> Result<Record, RequestResult> { 267 pub fn put_mutable(path: &Path, f: impl Read, expected_size: usize, key: &ResourceKey, auth: &AuthResult) -> Result<Record, RequestResult> { 268 let pointer = key.pointer_for(auth); 269 let mutable_ref = hex::encode(&pointer); 270 debug!("generated mutable ref {}", &mutable_ref); 271 let link_path_buf = path.join(&mutable_ref); 272 273 let record = put_immutable(path, f, expected_size); 274 match record { 275 Ok(v) => { 276 match remove_file(&link_path_buf) { 277 Ok(_) => { 278 debug!("unlinked mutable ref on {:?}", &link_path_buf); 279 }, 280 Err(e) => { 281 debug!("clear symlink failed {:?}", e); 282 } 283 }; 284 match symlink(&v.path, &link_path_buf) { 285 Ok(_) => { 286 info!("linked mutable ref {:?} -> {:?}", &link_path_buf, &v.path); 287 }, 288 Err(e) => { 289 error!("could not link mutable ref {:?} -> {:?} ({})", &link_path_buf, &v.path, e); 290 }, 291 292 }; 293 let r = Record{ 294 digest: pointer, 295 path: link_path_buf.clone(), 296 alias: Some(v.digest), 297 }; 298 return Ok(r); 299 }, 300 Err(e) => { 301 return Err(e); 302 } 303 } 304 } 305 306 307 /// Retrieve the content for a single record. 308 /// 309 /// # Arguments 310 /// 311 /// * `pointer` - A reference to the pointer. 312 /// * `path` - Absolute path to storage directory. 313 pub fn get(pointer: Vec<u8>, path: &Path) -> Option<File> { 314 _ = pointer; 315 let path_canon = match path.canonicalize() { 316 Ok(v) => { 317 v 318 }, 319 Err(_) => { 320 return None; 321 }, 322 }; 323 match File::open(path_canon) { 324 Ok(f) => { 325 return Some(f); 326 }, 327 _ => {}, 328 } 329 None 330 } 331 332 #[cfg(test)] 333 mod tests { 334 use super::ResourceKey; 335 use super::AuthResult; 336 use super::{ 337 put_immutable, 338 put_mutable, 339 }; 340 use std::fs::{ 341 read, 342 File, 343 }; 344 use std::io::Read; 345 use tempfile::tempdir; 346 use hex; 347 use std::str::FromStr; 348 349 use env_logger; 350 use log::{debug, info, error}; 351 352 #[test] 353 fn test_pointer() { 354 let resource = ResourceKey{ 355 v: vec!(0x66, 0x6f, 0x6f), 356 }; 357 let subject = AuthResult{ 358 identity: vec!(0x62, 0x61, 0x72), 359 error: false, 360 }; 361 let r = resource.pointer_for(&subject); 362 363 let foobar_digest = hex::decode("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2").unwrap(); 364 assert_eq!(r, foobar_digest); 365 } 366 367 #[test] 368 fn test_immutable() { 369 let d = tempdir().unwrap(); 370 let b = b"foo"; 371 put_immutable(d.path().clone(), &b[..], 3); 372 373 let immutable_path_buf = d.path().join("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); 374 let immutable_path = immutable_path_buf.as_path(); 375 debug!(">>>>> checking immutable path {:?}", immutable_path); 376 assert!(immutable_path.is_file()); 377 378 let mut r = read(immutable_path).unwrap(); 379 assert_eq!(r, b.to_vec()); 380 } 381 382 #[test] 383 fn test_mutable() { 384 let d = tempdir().unwrap(); 385 let b = b"foo"; 386 let k = ResourceKey::from_str("baz").unwrap(); 387 let auth_result = AuthResult{ 388 identity: Vec::from("bar"), 389 error: false, 390 }; 391 put_mutable(d.path().clone(), &b[..], 3, &k, &auth_result); 392 393 let foobar_hex = "561061c1c6b4fec065f5761e12f072b9591cf3ac55c70fe6fcbb39b0c16c6e20"; 394 let mutable_path_buf = d.path().join(foobar_hex); 395 let mutable_path = mutable_path_buf.as_path(); 396 debug!(">>>>> checking mutable path {:?}", mutable_path); 397 assert!(mutable_path.is_symlink()); 398 399 let mut r = read(mutable_path).unwrap(); 400 assert_eq!(r, b.to_vec()); 401 402 let immutable_path_buf = d.path().join("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); 403 let immutable_path = immutable_path_buf.as_path(); 404 debug!(">>>>> checking immutable path {:?}", immutable_path); 405 assert!(immutable_path.is_file()); 406 407 let mut r = read(immutable_path).unwrap(); 408 assert_eq!(r, b.to_vec()); 409 } 410 411 #[test] 412 fn test_mutable_overwrite() { 413 let d = tempdir().unwrap(); 414 let mut b = b"foo"; 415 let k = ResourceKey::from_str("baz").unwrap(); 416 let mut auth_result = AuthResult{ 417 identity: Vec::from("bar"), 418 error: false, 419 }; 420 let result: Vec<u8> = vec!(); 421 let r = put_mutable(d.path().clone(), &b[..], 3, &k, &auth_result).unwrap(); 422 423 let foobar_hex = "561061c1c6b4fec065f5761e12f072b9591cf3ac55c70fe6fcbb39b0c16c6e20"; 424 let mutable_path_buf = d.path().join(foobar_hex); 425 let mutable_path = mutable_path_buf.as_path(); 426 427 let mut f = File::open(&mutable_path).unwrap(); 428 let mut result_behind: Vec<u8> = vec!(); 429 f.read_to_end(&mut result_behind); 430 let mut result_expect = "foo".as_bytes(); 431 assert_eq!(result_behind, result_expect); 432 433 b = b"bar"; 434 auth_result = AuthResult{ 435 identity: Vec::from("bar"), 436 error: false, 437 }; 438 let r = put_mutable(d.path().clone(), &b[..], 3, &k, &auth_result).unwrap(); 439 440 f = File::open(&mutable_path).unwrap(); 441 result_behind = vec!(); 442 f.read_to_end(&mut result_behind); 443 result_expect = "bar".as_bytes(); 444 assert_eq!(result_behind, result_expect); 445 } 446 }