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