wala-rust

Content-adressed HTTP file server
Info | Log | Files | Refs | README | LICENSE

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 }