sn0int 0.25.0

Semi-automatic OSINT framework and package manager
Documentation
use crate::errors::*;
use crate::paths;
use crate::worker::{EventWithCallback, Event2};
use crate::workspaces::Workspace;

use bytes::Bytes;
use std::fs;
use std::path::{Path, PathBuf};
use std::result;
use std::sync::mpsc;

pub use sn0int_std::blobs::Blob;

impl EventWithCallback for Blob {
    type Payload = ();

    #[inline(always)]
    fn with_callback(self, tx: mpsc::Sender<result::Result<Self::Payload, String>>) -> Event2 {
        Event2::Blob((self, tx))
    }
}

pub struct BlobStorage {
    path: PathBuf,
}

impl BlobStorage {
    #[inline]
    pub fn new<I: Into<PathBuf>>(path: I) -> BlobStorage {
        BlobStorage {
            path: path.into(),
        }
    }

    #[inline]
    pub fn workspace(workspace: &Workspace) -> Result<BlobStorage> {
        let path = paths::blobs_dir(workspace)?;
        Ok(BlobStorage::new(path))
    }

    #[inline]
    pub fn join(&self, id: &str) -> Result<PathBuf> {
        if !id.chars().all(char::is_alphanumeric) {
            bail!("blob id contains invalid characters");
        }
        Ok(self.path.join(id))
    }

    #[inline(always)]
    pub fn path(&self) -> &Path {
        self.path.as_ref()
    }

    pub fn load(&self, id: &str) -> Result<Blob> {
        let path = self.join(id)?;

        debug!("Loading blob from {:?}", path);
        let bytes = fs::read(path)
            .context("Failed to read blob")?;

        Ok(Blob {
            id: id.to_string(),
            bytes: Bytes::from(bytes),
        })
    }

    pub fn save(&self, blob: &Blob) -> Result<()> {
        let path = self.join(&blob.id)?;

        debug!("Writing blob to {:?}", path);
        fs::write(path, &blob.bytes)
            .context("Failed to write blob")?;

        Ok(())
    }

    pub fn delete(&self, id: &str) -> Result<()> {
        let path = self.join(id)?;
        debug!("Deleting blob: {:?}", path);
        fs::remove_file(path)
            .context("Failed to delete blob")?;
        Ok(())
    }

    pub fn list(&self) -> Result<Vec<String>> {
        let mut blobs = Vec::new();
        for entry in fs::read_dir(&self.path)? {
            let blob = entry?
                .file_name()
                .into_string()
                .map_err(|_| format_err!("Invalid filename"))?;
            blobs.push(blob);
        }
        Ok(blobs)
    }

    pub fn stat(&self, id: &str) -> Result<u64> {
        let path = self.join(id)?;
        debug!("Stat-ing blob: {:?}", path);
        let md = fs::metadata(path)
            .context("Failed to stat blob")?;
        Ok(md.len())
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    use tempfile;

    #[inline]
    fn blob() -> (Bytes, Blob) {
        let bytes = Bytes::from(&b"asdf"[..]);
        (bytes.clone(), Blob::create(bytes))
    }

    #[test]
    fn test_blobstorage_save() {
        let dir = tempfile::tempdir().expect("tempdir");
        let s = BlobStorage::new(dir.path());

        let (_, blob1) = blob();
        s.save(&blob1).expect("save failed");

        let blob2 = s.load("DTTV3EjpHBNJx3Zw7eJsVPm4bYXKmNkJQpVNkcvTtTSz").expect("load failed");

        assert_eq!(blob1, blob2);
    }

    #[test]
    fn test_blobstorage_load_failure() {
        let dir = tempfile::tempdir().expect("tempdir");
        let s = BlobStorage::new(dir.path());

        let result = s.load("DTTV3EjpHBNJx3Zw7eJsVPm4bYXKmNkJQpVNkcvTtTSz");

        assert!(result.is_err());
    }

    #[test]
    fn test_path_validation() {
        let dir = tempfile::tempdir().expect("tempdir");
        let s = BlobStorage::new(dir.path());

        let result = s.load("../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../etc/passwd");

        assert!(result.is_err());
    }
}