wala-rust

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

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 }