diff --git a/src/main.rs b/src/main.rs index 1eb9cce..7c5fa0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,9 @@ extern crate snap; mod chacha_io; mod pgp; +mod snippet; +use crate::snippet::*; use crate::pgp::KnownKeys; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use chacha_io::{ChaChaReader, ChaChaWriter}; @@ -29,145 +31,8 @@ use std::io::prelude::*; use std::iter::Iterator; use std::path::{Path, PathBuf}; use std::sync::Mutex; +use std::sync::Arc; -struct Snippet<'a> { - id: String, - storage: &'a SnippetStorage<'a>, -} - -#[allow(dead_code)] -struct SnippetMeta { - created: DateTime, - compression: Option, -} - -struct SnippetStorage<'a> { - directory: &'a Path, -} - -impl<'a> Snippet<'a> { - pub fn random(storage: &'a SnippetStorage) -> Snippet<'a> { - Snippet::new( - &rand::thread_rng() - .gen_ascii_chars() - .take(8) - .collect::(), - storage, - ) - } - - pub fn new(id: &str, storage: &'a SnippetStorage) -> Snippet<'a> { - Snippet { - id: id.to_string(), - storage, - } - } - - pub fn file_id(&self) -> String { - SnippetStorage::file_id(&self.id) - } - - pub fn passphrase(&self) -> ([u8; 8], [u8; 32]) { - let mut hasher = Sha256::new(); - hasher.input(self.id.as_bytes()); - let res = hasher.result(); - let nonce: [u8; 8] = res[0..8].try_into().unwrap(); - let mut hasher = Sha256::new(); - hasher.input(self.id.as_bytes()); - hasher.input(b"pass"); - let pass: [u8; 32] = res[0..32].try_into().unwrap(); - (nonce, pass) - } - - pub fn path(&self) -> PathBuf { - self.storage.directory.join(&self.file_id()) - } - - pub fn metadata(&self) -> Result { - let mut file = File::open(self.path())?; - let (nonce, key) = self.passphrase(); - self.metadata_via_handle(&mut ChaChaReader::new(&key, &nonce, &mut file)) - } - - fn metadata_via_handle(&self, hdl: &mut impl Read) -> Result { - let timestamp = hdl.read_i64::()?; - let comp_len = hdl.read_u16::()? as usize; - let mut comp = Vec::with_capacity(comp_len); - comp.resize(comp_len, 0u8); - hdl.read_exact(&mut comp)?; - let comp = String::from_utf8(comp).unwrap(); - Ok(SnippetMeta { - created: Utc.timestamp_millis(timestamp), - compression: Some(comp).filter(|_| comp_len > 0), - }) - } - - fn contents(&self) -> Result { - let mut file = File::open(self.path())?; - let (nonce, key) = self.passphrase(); - let mut reader = ChaChaReader::new(&key, &nonce, &mut file); - let meta = self.metadata_via_handle(&mut reader)?; - fn read_string(r: &mut impl Read) -> Result { - let mut text = String::new(); - r.read_to_string(&mut text)?; - Ok(text) - } - dbg!((&meta.compression, &meta.created, self.file_id())); - match meta.compression { - Some(ref comp) if comp == "snap" => { - let mut r = snap::Reader::new(&mut reader); - read_string(&mut r) - } - _ => read_string(&mut reader), - } - } - - fn write(self, content: &str) -> Result, io::Error> { - let mut file = File::create(self.path())?; - let (nonce, key) = self.passphrase(); - let mut writer = ChaChaWriter::new(&key, &nonce, &mut file); - writer.write_i64::(Utc::now().timestamp())?; - let comp = if content.len() > 2048 { - Some("snap") - } else { - None - }; - writer.write_u16::(comp.map(|s| s.len() as u16).unwrap_or(0u16))?; - writer.write(comp.map(|s| s.as_bytes()).unwrap_or(&[0u8; 0]))?; - match comp { - Some(ref comp) if comp == &"snap" => { - let mut w = snap::Writer::new(&mut writer); - w.write_all(content.as_bytes())? - } - _ => writer.write_all(content.as_bytes())?, - }; - Ok(Snippet::new(&self.id, self.storage)) - } -} - -impl<'a> SnippetStorage<'a> { - pub fn new(directory: &'a Path) -> SnippetStorage<'a> { - SnippetStorage { - directory: directory, - } - } - pub fn file_id(id: &str) -> String { - let mut hasher = Sha256::new(); - hasher.input(id.as_bytes()); - hex::encode(&hasher.result()[0..12]) - } - fn has(&self, id: &str) -> bool { - self.directory.join(Self::file_id(id)).exists() - } - - fn open(&self, id: &str) -> Option { - if self.has(id) { - Some(Snippet::new(id, self)) - } else { - None - } - } -} #[cfg(not(debug_assertions))] const STORAGE_DIR: &str = "/snips"; @@ -176,9 +41,9 @@ const STORAGE_DIR: &str = "/snips"; const STORAGE_DIR: &str = "/tmp"; lazy_static! { - static ref KNOWN_KEYS: Mutex = Mutex::new( + static ref KNOWN_KEYS: Arc> = Arc::new(Mutex::new( KnownKeys::load_dir([STORAGE_DIR, "keys"].join("/")).expect("Failed to load pubkeys") - ); + )); } const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/snippet.rs b/src/snippet.rs new file mode 100644 index 0000000..82419d2 --- /dev/null +++ b/src/snippet.rs @@ -0,0 +1,161 @@ +use crate::pgp::KnownKeys; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use crate::chacha_io::{ChaChaReader, ChaChaWriter}; +use chrono::*; +use core::cell::RefCell; +use iron::method::Method; +use iron::modifiers::Redirect; +use iron::prelude::*; +use iron::url::Url; +use rand::Rng; +use sha2::{Digest, Sha256}; +use std::borrow::BorrowMut; +use std::convert::TryInto; +use std::fs; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::iter::Iterator; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use std::sync::Arc; + +pub struct Snippet<'a> { + pub id: String, + pub storage: &'a SnippetStorage<'a>, +} + +#[allow(dead_code)] +pub struct SnippetMeta { + created: DateTime, + compression: Option, +} + +pub struct SnippetStorage<'a> { + directory: &'a Path, +} + +impl<'a> Snippet<'a> { + pub fn random(storage: &'a SnippetStorage) -> Snippet<'a> { + Snippet::new( + &rand::thread_rng() + .gen_ascii_chars() + .take(8) + .collect::(), + storage, + ) + } + + pub fn new(id: &str, storage: &'a SnippetStorage) -> Snippet<'a> { + Snippet { + id: id.to_string(), + storage, + } + } + + pub fn file_id(&self) -> String { + SnippetStorage::file_id(&self.id) + } + + pub fn passphrase(&self) -> ([u8; 8], [u8; 32]) { + let mut hasher = Sha256::new(); + hasher.input(self.id.as_bytes()); + let res = hasher.result(); + let nonce: [u8; 8] = res[0..8].try_into().unwrap(); + let mut hasher = Sha256::new(); + hasher.input(self.id.as_bytes()); + hasher.input(b"pass"); + let pass: [u8; 32] = res[0..32].try_into().unwrap(); + (nonce, pass) + } + + pub fn path(&self) -> PathBuf { + self.storage.directory.join(&self.file_id()) + } + + pub fn metadata(&self) -> Result { + let mut file = File::open(self.path())?; + let (nonce, key) = self.passphrase(); + self.metadata_via_handle(&mut ChaChaReader::new(&key, &nonce, &mut file)) + } + + fn metadata_via_handle(&self, hdl: &mut impl Read) -> Result { + let timestamp = hdl.read_i64::()?; + let comp_len = hdl.read_u16::()? as usize; + let mut comp = Vec::with_capacity(comp_len); + comp.resize(comp_len, 0u8); + hdl.read_exact(&mut comp)?; + let comp = String::from_utf8(comp).unwrap(); + Ok(SnippetMeta { + created: Utc.timestamp_millis(timestamp), + compression: Some(comp).filter(|_| comp_len > 0), + }) + } + + pub fn contents(&self) -> Result { + let mut file = File::open(self.path())?; + let (nonce, key) = self.passphrase(); + let mut reader = ChaChaReader::new(&key, &nonce, &mut file); + let meta = self.metadata_via_handle(&mut reader)?; + fn read_string(r: &mut impl Read) -> Result { + let mut text = String::new(); + r.read_to_string(&mut text)?; + Ok(text) + } + dbg!((&meta.compression, &meta.created, self.file_id())); + match meta.compression { + Some(ref comp) if comp == "snap" => { + let mut r = snap::Reader::new(&mut reader); + read_string(&mut r) + } + _ => read_string(&mut reader), + } + } + + pub fn write(self, content: &str) -> Result, io::Error> { + let mut file = File::create(self.path())?; + let (nonce, key) = self.passphrase(); + let mut writer = ChaChaWriter::new(&key, &nonce, &mut file); + writer.write_i64::(Utc::now().timestamp())?; + let comp = if content.len() > 2048 { + Some("snap") + } else { + None + }; + writer.write_u16::(comp.map(|s| s.len() as u16).unwrap_or(0u16))?; + writer.write(comp.map(|s| s.as_bytes()).unwrap_or(&[0u8; 0]))?; + match comp { + Some(ref comp) if comp == &"snap" => { + let mut w = snap::Writer::new(&mut writer); + w.write_all(content.as_bytes())? + } + _ => writer.write_all(content.as_bytes())?, + }; + Ok(Snippet::new(&self.id, self.storage)) + } +} + +impl<'a> SnippetStorage<'a> { + pub fn new(directory: &'a Path) -> SnippetStorage<'a> { + SnippetStorage { + directory: directory, + } + } + pub fn file_id(id: &str) -> String { + let mut hasher = Sha256::new(); + hasher.input(id.as_bytes()); + hex::encode(&hasher.result()[0..12]) + } + fn has(&self, id: &str) -> bool { + self.directory.join(Self::file_id(id)).exists() + } + + pub fn open(&self, id: &str) -> Option { + if self.has(id) { + Some(Snippet::new(id, self)) + } else { + None + } + } +} +