diff --git a/src/auth/mod.rs b/src/auth/mod.rs index facd836..c568420 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -11,16 +11,21 @@ /// /// struct RandomAuthenticator; /// -/// impl Authenticator for RandomAuthenticator { -/// fn authenticate(&self, _username: &str, _password: &str) -> Result { -/// Ok(rand::random()) +/// impl Authenticator for RandomAuthenticator { +/// fn authenticate(&self, _username: &str, _password: &str) -> Result { +/// match rand::random() { +/// true => Ok(RandomUser{}), +/// _ => Err(()), +/// } /// } /// } +/// +/// struct RandomUser; /// ``` /// [`Server`]: ../server/struct.Server.html -pub trait Authenticator { +pub trait Authenticator { /// Authenticate the given user with the given password. - fn authenticate(&self, username: &str, password: &str) -> Result; + fn authenticate(&self, username: &str, password: &str) -> Result; } /// [`Authenticator`] implementation that authenticates against [`PAM`]. @@ -35,15 +40,19 @@ pub mod pam; /// # Example /// /// ```rust -/// use firetrap::auth::{Authenticator, AnonymousAuthenticator}; +/// use firetrap::auth::{Authenticator, AnonymousUser, AnonymousAuthenticator}; /// /// let my_auth = AnonymousAuthenticator{}; -/// assert_eq!(my_auth.authenticate("Finn", "I ❤️ PB").unwrap(), true); +/// assert_eq!(my_auth.authenticate("Finn", "I ❤️ PB").unwrap(), AnonymousUser{}); /// ``` pub struct AnonymousAuthenticator; -impl Authenticator for AnonymousAuthenticator { - fn authenticate(&self, _username: &str, _password: &str) -> Result { - Ok(true) +impl Authenticator for AnonymousAuthenticator { + fn authenticate(&self, _username: &str, _password: &str) -> Result { + Ok(AnonymousUser{}) } } + +/// AnonymousUser +#[derive(Debug,PartialEq)] +pub struct AnonymousUser; diff --git a/src/commands.rs b/src/commands.rs index a3beb83..be088e7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -69,7 +69,7 @@ pub enum Command { /// The `STAT` command Stat { /// The bytes making up the path about which information is requested, if given. - path: Option, + path: Option, }, /// The `TYPE` command Type, @@ -193,9 +193,9 @@ impl Command { } } b"SYST" | b"syst" => Command::Syst, - b"STAT" => { - let params = parse_to_eol(cmd_params)?; - let path = if !params.is_empty() { Some(params) } else { None }; + b"STAT" | b"stat" => { + let path = parse_to_eol(cmd_params)?; + let path = if path.is_empty() { None } else { Some(String::from_utf8_lossy(&path).to_string()) }; Command::Stat{path} }, b"TYPE" | b"type" => { @@ -700,6 +700,16 @@ mod tests { assert_eq!(Command::parse(input).unwrap(), Command::Port); } + #[test] + fn parse_stat() { + let input = "STAT\r\n"; + assert_eq!(Command::parse(input), Ok(Command::Stat{path: None})); + + let input = "STAT tmp\r\n"; + let expected_path = Some("tmp".to_string()); + assert_eq!(Command::parse(input), Ok(Command::Stat{path: expected_path})); + } + #[test] fn parse_list() { let input = "LIST\r\n"; diff --git a/src/server.rs b/src/server.rs index 72c346e..c5b9d30 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,7 +13,8 @@ use uuid::Uuid; use log::{info, warn, error}; use crate::auth; -use crate::auth::Authenticator; +use crate::auth::{AnonymousUser, Authenticator, AnonymousAuthenticator}; + use crate::storage; use crate::commands; use crate::commands::Command; @@ -39,6 +40,8 @@ enum InternalMsg { SendingData, // Unknown Error retrieving file UnknownRetrieveError, + // Stat successfully + StatSuccess, // Listed the directory successfully DirectorySuccesfullyListed, // File succesfully deleted @@ -230,14 +233,15 @@ enum SessionState { } // This is where we keep the state for a ftp session. -struct Session - where S: storage::StorageBackend, - ::File: tokio_io::AsyncRead + Send, - ::Metadata: storage::Metadata, - ::Error: Send, +struct Session + where S: storage::StorageBackend, + >::File: tokio_io::AsyncRead + Send, + >::Metadata: storage::Metadata, + >::Error: Send, { - username: Option, + user: Arc>, storage: Arc, + username: Option, data_cmd_tx: Option>, data_cmd_rx: Option>, data_abort_tx: Option>, @@ -254,16 +258,17 @@ enum DataCommand { Abort, } -impl Session - where S: storage::StorageBackend + Send + Sync + 'static, - ::File: tokio_io::AsyncRead + Send, - ::Metadata: storage::Metadata, - ::Error: Send, +impl Session + where S: storage::StorageBackend + Send + Sync + 'static, + >::File: tokio_io::AsyncRead + Send, + >::Metadata: storage::Metadata, + >::Error: Send, { fn with_storage(storage: Arc) -> Self { Session { - username: None, + user: Arc::new(None), storage, + username: None, data_cmd_tx: None, data_cmd_rx: None, data_abort_tx: None, @@ -277,7 +282,7 @@ impl Session /// socket: the data socket we'll be working with /// tx: channel to send the result of our operation on /// rx: channel to receive the command on - fn process_data(&mut self, socket: TcpStream, tx: mpsc::Sender) { + fn process_data(&mut self, user: Arc>, socket: TcpStream, tx: mpsc::Sender) { // TODO: Either take the rx as argument, or properly check the result instead of // `unwrap()`. let rx = self.data_cmd_rx.take().unwrap(); @@ -286,6 +291,7 @@ impl Session let abort_rx = self.data_abort_rx.take().unwrap(); let storage = Arc::clone(&self.storage); let cwd = self.cwd.clone(); + let user = user.clone(); let task = rx .take(1) .map(DataCommand::ExternalCommand) @@ -298,12 +304,13 @@ impl Session .into_future() .map(move |(cmd, _)| { use self::DataCommand::ExternalCommand; + let user = user.clone(); match cmd { Some(ExternalCommand(Command::Retr{path})) => { let tx_sending = tx.clone(); let tx_error = tx.clone(); tokio::spawn( - storage.get(path) + storage.get(&user, path) .map_err(|_| std::io::Error::new(ErrorKind::Other, "Failed to get file")) .and_then(|f| { tx_sending.send(InternalMsg::SendingData) @@ -336,7 +343,7 @@ impl Session let tx_ok = tx.clone(); let tx_error = tx.clone(); tokio::spawn( - storage.put(socket, path) + storage.put(&user, socket, path) .map_err(|_| std::io::Error::new(ErrorKind::Other, "Failed to put file")) .and_then(|_| { tx_ok.send(InternalMsg::WrittenData) @@ -358,6 +365,40 @@ impl Session }) ); }, + Some(ExternalCommand(Command::Stat{path})) => { + + // TODO: Implement carefully in the future + let path = match path { + Some(path) => cwd.join(path), + None => cwd, + }; + let tx_ok = tx.clone(); + let tx_error = tx.clone(); + tokio::spawn( + storage.stat_fmt(&user, path) + .and_then(|cursor| { + tokio::io::copy(cursor, socket) + }) + .and_then(|_| { + tx_ok + .send(InternalMsg::StatSuccess) + .map_err(|_| std::io::Error::new(ErrorKind::Other, "Failed to Send `StatSuccess` event")) + }) + .or_else(|e| { + let msg = match e.kind() { + ErrorKind::NotFound => InternalMsg::NotFound, + ErrorKind::PermissionDenied => InternalMsg::PermissionDenied, + ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => InternalMsg::ConnectionReset, + _ => InternalMsg::WriteFailed, + }; + tx_error.send(msg) + }) + .map(|_| ()) + .map_err(|e| { + warn!("Failed to send stat: {:?}", e); + }) + ); + }, Some(ExternalCommand(Command::List{path})) => { let path = match path { Some(path) => cwd.join(path), @@ -366,7 +407,7 @@ impl Session let tx_ok = tx.clone(); let tx_error = tx.clone(); tokio::spawn( - storage.list_fmt(path) + storage.list_fmt(&user, path) .and_then(|res| tokio::io::copy(res, socket)) .and_then(|_| { tx_ok.send(InternalMsg::DirectorySuccesfullyListed) @@ -397,7 +438,7 @@ impl Session let tx_ok = tx.clone(); let tx_error = tx.clone(); tokio::spawn( - storage.nlst(path) + storage.nlst(&user, path) .and_then(|res| tokio::io::copy(res, socket)) .and_then(|_| { tx_ok.send(InternalMsg::DirectorySuccesfullyListed) @@ -456,16 +497,16 @@ impl Session /// /// [`Authenticator`]: ../auth/trait.Authenticator.html /// [`StorageBackend`]: ../storage/trait.StorageBackend.html -pub struct Server - where S: storage::StorageBackend +pub struct Server + where S: storage::StorageBackend { storage: Box<(Fn() -> S) + Send>, greeting: &'static str, - authenticator: &'static (Authenticator + Send + Sync), + authenticator: &'static (Authenticator + Send + Sync), passive_addrs: Arc>, } -impl Server { +impl Server { /// Create a new `Server` with the given filesystem root. /// /// # Example @@ -485,25 +526,51 @@ impl Server { }; server.passive_ports(49152..65535) } - } -impl Server - where S: 'static + storage::StorageBackend + Sync + Send, - ::File: tokio_io::AsyncRead + Send, - ::Metadata: storage::Metadata, - ::Error: Send, +impl Server + where S: 'static + storage::StorageBackend + Sync + Send, + >::File: tokio_io::AsyncRead + Send, + >::Metadata: storage::Metadata, + >::Error: Send, { /// Construct a new [`Server`] with the given [`StorageBackend`]. The other parameters will be /// set to defaults. /// /// [`Server`]: struct.Server.html /// [`StorageBackend`]: ../storage/trait.StorageBackend.html - pub fn new(s: Box S + Send>) -> Self { + pub fn new(s: Box S + Send>) -> Server + where S: 'static + storage::StorageBackend + Sync + Send, + >::File: tokio_io::AsyncRead + Send, + >::Metadata: storage::Metadata, + >::Error: Send, + { let server = Server { storage: s, greeting: "Welcome to the firetrap FTP server", - authenticator: &auth::AnonymousAuthenticator{}, + authenticator: &AnonymousAuthenticator{}, + passive_addrs: Arc::new(vec![]), + }; + server.passive_ports(49152..65535) + } + + /// Construct a new [`Server`] with the given [`StorageBackend`]. The other parameters will be + /// set to defaults. + /// + /// [`Server`]: struct.Server.html + /// [`StorageBackend`]: ../storage/trait.StorageBackend.html + pub fn with_authenticator(s: Box S + Send>, a: &'static A) -> Server + where S: 'static + storage::StorageBackend + Sync + Send, + A: auth::Authenticator + Send + Sync, + // U: 'static + auth::Authenticator + Send + Sync, + >::File: tokio_io::AsyncRead + Send, + >::Metadata: storage::Metadata, + >::Error: Send, + { + let server = Server { + storage: s, + greeting: "Welcome to the firetrap FTP server", + authenticator: a, passive_addrs: Arc::new(vec![]), }; server.passive_ports(49152..65535) @@ -554,27 +621,6 @@ impl Server self } - /// Set the [`Authenticator`] that will be used for authentication. - /// - /// # Example - /// - /// ```rust - /// use firetrap::{auth, auth::AnonymousAuthenticator, Server}; - /// - /// // Use it in a builder-like pattern: - /// let mut server = Server::with_root("/tmp").authenticator(&auth::AnonymousAuthenticator{}); - /// - /// // Or instead if you prefer: - /// let mut server = Server::with_root("/tmp"); - /// server.authenticator(&auth::AnonymousAuthenticator{}); - /// ``` - /// - /// [`Authenticator`]: ../auth/trait.Authenticator.html - pub fn authenticator(mut self, authenticator: &'static A) -> Self { - self.authenticator = authenticator; - self - } - /// Start the server and listen for connections on the given address. /// /// # Example @@ -669,15 +715,15 @@ impl Server match session.state { WaitPass => { let pass = std::str::from_utf8(&password)?; - let user = session.username.clone().unwrap(); - let res = authenticator.authenticate(&user, pass); - match res { - Ok(true) => { + let name = &session.username.clone().unwrap(); + let user = authenticator.authenticate(&name, pass); + match user { + Ok(user) => { + session.user = Arc::new(Some(user)); session.state = WaitCmd; Ok("230 User logged in, proceed\r\n".to_string()) } - Ok(false) => Ok("530 Wrong username or password\r\n".to_string()), - Err(_) => { + _ => { warn!("Unknown Authentication backend failure"); Ok("530 Failed to authenticate\r\n".to_string()) } @@ -691,17 +737,15 @@ impl Server // the capabilities of the other peer. D.J. Bernstein recommends to just respond with // `UNIX Type: L8` for greatest compatibility. Command::Syst => respond!(|| Ok("215 UNIX Type: L8\r\n".to_string())), - Command::Stat{path} => { + Command::Stat{ .. } => { ensure_authenticated!(); - match path { - None => Ok("211 I'm just a humble FTP server\r\n".to_string()), - Some(path) => { - let path = std::str::from_utf8(&path)?; - // TODO: Implement :) - info!("Got command STAT {}, but we don't support parameters yet\r\n", path); - Ok("504 Stat with paths unsupported atm\r\n".to_string()) - }, - } + let mut session = session.lock()?; + let tx = match session.data_cmd_tx.take() { + Some(tx) => tx, + None => return Ok("425 No data connection established\r\n".to_string()), + }; + spawn!(tx.send(cmd.clone())); + Ok("211 STAT END\r\n".to_string()) }, Command::Acct{ .. } => respond!(|| Ok("530 I don't know accounting man\r\n".to_string())), Command::Type => respond!(|| Ok("200 I'm always in binary mode, dude...\r\n".to_string())), @@ -740,32 +784,35 @@ impl Server let (cmd_tx, cmd_rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(1); let (data_abort_tx, data_abort_rx): (mpsc::Sender<()>, mpsc::Receiver<()>) = mpsc::channel(1); + { - let mut session = session.lock()?; - session.data_cmd_tx = Some(cmd_tx); - session.data_cmd_rx = Some(cmd_rx); - session.data_abort_tx = Some(data_abort_tx); - session.data_abort_rx = Some(data_abort_rx); + let mut session = session.lock()?; + session.data_cmd_tx = Some(cmd_tx); + session.data_cmd_rx = Some(cmd_rx); + session.data_abort_tx = Some(data_abort_tx); + session.data_abort_rx = Some(data_abort_rx); } let session = session.clone(); tokio::spawn( Box::new( - listener.incoming() - .take(1) - .map_err(|e| warn!("Failed to accept data socket: {:?}", e)) - .for_each(move |socket| { - let tx = tx.clone(); - let session = session.clone(); - let mut session = session.lock().unwrap_or_else(|res| { - // TODO: Send signal to `tx` here, so we can handle the - // error - error!("session lock() result: {}", res); - panic!() - }); - session.process_data(socket, tx); - Ok(()) - }) + listener + .incoming() + .take(1) + .map_err(|e| warn!("Failed to accept data socket: {:?}", e)) + .for_each(move |socket| { + let tx = tx.clone(); + let session = session.clone(); + let mut session = session.lock().unwrap_or_else(|res| { + // TODO: Send signal to `tx` here, so we can handle the + // error + error!("session lock() result: {}", res); + panic!() + }); + let user = session.user.clone(); + session.process_data(user, socket, tx); + Ok(()) + }) ) ); @@ -861,7 +908,7 @@ impl Server let tx_success = tx.clone(); let tx_fail = tx.clone(); tokio::spawn( - storage.del(path) + storage.del(&session.user, path) .map_err(|_| std::io::Error::new(ErrorKind::Other, "Failed to delete file")) .and_then(|_| { tx_success.send(InternalMsg::DelSuccess) @@ -890,7 +937,7 @@ impl Server let tx_success = tx.clone(); let tx_fail = tx.clone(); tokio::spawn( - storage.mkd(&path) + storage.mkd(&session.user, &path) .map_err(|_| std::io::Error::new(ErrorKind::Other, "Failed to create directory")) .and_then(|_| { tx_success.send(InternalMsg::MkdirSuccess(path)) @@ -950,7 +997,7 @@ impl Server let storage = Arc::clone(&session.storage); match session.rename_from.take() { Some(from) => { - spawn!(storage.rename(from, file)); + spawn!(storage.rename(&session.user, from, file)); Ok("250 sure, it shall be known\r\n".to_string()) }, None => Ok("450 Please tell me what file you want to rename first\r\n".to_string()) @@ -968,6 +1015,7 @@ impl Server Event::InternalMsg(WrittenData) => Ok("226 File succesfully written\r\n".to_string()), Event::InternalMsg(UnknownRetrieveError) => Ok("450 Unknown Error\r\n".to_string()), Event::InternalMsg(DirectorySuccesfullyListed) => Ok("226 Listed the directory\r\n".to_string()), + Event::InternalMsg(StatSuccess) => Ok("211 Listed the directory successfully\r\n".to_string()), Event::InternalMsg(DelSuccess) => Ok("250 File successfully removed\r\n".to_string()), Event::InternalMsg(DelFail) => Ok("450 Failed to delete the file\r\n".to_string()), // The InternalMsg::Quit will never be reached, because we catch it in the task before diff --git a/src/storage.rs b/src/storage.rs index cc83cc3..0a9b4af 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -71,7 +71,7 @@ impl std::fmt::Display for Fileinfo /// /// [`Server`]: ../server/struct.Server.html /// [`filesystem`]: ./struct.Filesystem.html -pub trait StorageBackend { +pub trait StorageBackend { /// The concrete type of the Files returned by this StorageBackend. type File; /// The concrete type of the `Metadata` used by this StorageBackend. @@ -82,21 +82,48 @@ pub trait StorageBackend { /// Returns the `Metadata` for the given file. /// /// [`Metadata`]: ./trait.Metadata.html - fn stat>(&self, path: P) -> Box + Send>; + fn stat>(&self, user: &Option, path: P) -> Box, Error = Self::Error> + Send> where >::Metadata: Metadata; + + /// Returns some bytes that make up a file that can immediately be sent to the client. + fn stat_fmt>(&self, user: &Option, path: P) -> Box>, Error = std::io::Error> + Send> + where >::Metadata: Metadata + 'static, + >::Error: Send + 'static, + { + println!("STAT_FMT"); + + let res = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); + let res_work = res.clone(); + let future_cursor = self + .stat(user, path) + .map(move |file| { + let mut res = res_work.lock().unwrap(); + let bytes = format!("{}\r\n", file).into_bytes(); + res.extend_from_slice(&bytes); + }) + .map(move |_| { + std::sync::Arc::try_unwrap(res).expect("failed try_unwrap").into_inner().unwrap() + }) + .map(move |res| { + std::io::Cursor::new(res) + }) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "stat error")); + + Box::new(future_cursor) + } /// Returns the list of files in the given directory. - fn list>(&self, path: P) -> Box, Error = Self::Error> + Send> where ::Metadata: Metadata; + fn list>(&self, user: &Option, path: P) -> Box, Error = Self::Error> + Send> where >::Metadata: Metadata; /// Returns some bytes that make up a directory listing that can immediately be sent to the /// client. - fn list_fmt>(&self, path: P) -> Box>, Error = std::io::Error> + Send> - where ::Metadata: Metadata + 'static, - ::Error: Send + 'static, + fn list_fmt>(&self, user: &Option, path: P) -> Box>, Error = std::io::Error> + Send> + where >::Metadata: Metadata + 'static, + >::Error: Send + 'static, { let res = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); - let stream: Box, Error = Self::Error> + Send> = self.list(path); + let stream: Box, Error = Self::Error> + Send> = self.list(user, path); let res_work = res.clone(); let fut = stream.for_each(move |file: Fileinfo| { let mut res = res_work.lock().unwrap(); @@ -120,13 +147,13 @@ pub trait StorageBackend { /// Returns some bytes that make up a NLST directory listing (only the basename) that can /// immediately be sent to the client. - fn nlst>(&self, path: P) -> Box>, Error = std::io::Error> + Send> - where ::Metadata: Metadata + 'static, - ::Error: Send + 'static, + fn nlst>(&self, user: &Option, path: P) -> Box>, Error = std::io::Error> + Send> + where >::Metadata: Metadata + 'static, + >::Error: Send + 'static, { let res = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); - let stream: Box, Error = Self::Error> + Send> = self.list(path); + let stream: Box, Error = Self::Error> + Send> = self.list(user, path); let res_work = res.clone(); let fut = stream.for_each(move |file: Fileinfo| { let mut res = res_work.lock().unwrap(); @@ -152,19 +179,19 @@ pub trait StorageBackend { // TODO: Future versions of Rust will probably allow use to use `impl Future<...>` here. Use it // if/when available. By that time, also see if we can replace Self::File with the AsyncRead // Trait. - fn get>(&self, path: P) -> Box + Send>; + fn get>(&self, user: &Option, path: P) -> Box + Send>; /// Write the given bytes to the given file. - fn put, R: tokio::prelude::AsyncRead + Send + 'static>(&self, bytes: R, path: P) -> Box + Send>; + fn put, R: tokio::prelude::AsyncRead + Send + 'static>(&self, user: &Option, bytes: R, path: P) -> Box + Send>; /// Delete the given file. - fn del>(&self, path: P) -> Box + Send>; + fn del>(&self, user: &Option, path: P) -> Box + Send>; /// Create the given directory. - fn mkd>(&self, path: P) -> Box + Send>; + fn mkd>(&self, user: &Option, path: P) -> Box + Send>; /// Rename the given file to the given filename. - fn rename>(&self, from: P, to: P) -> Box + Send>; + fn rename>(&self, user: &Option, from: P, to: P) -> Box + Send>; } /// StorageBackend that uses a local filesystem, like a traditional FTP server. @@ -219,22 +246,32 @@ impl Filesystem { } } -impl StorageBackend for Filesystem { +impl StorageBackend for Filesystem { + type File = tokio::fs::File; type Metadata = std::fs::Metadata; type Error = Error; - fn stat>(&self, path: P) -> Box + Send> { + fn stat>(&self, _user: &Option, path: P) -> Box, Error = Self::Error> + Send> { let full_path = match self.full_path(path) { Ok(path) => path, Err(err) => return Box::new(future::err(err)), }; + // TODO: Some more useful error reporting - Box::new(tokio::fs::symlink_metadata(full_path).map_err(|_| Error::IOError)) + let clone_full_path = full_path.clone(); + let item = tokio::fs::symlink_metadata(full_path) + .map(move |metadata| Fileinfo { + path: std::path::PathBuf::from(clone_full_path.clone().to_str().unwrap()), + metadata, + }) + .map_err(|_| Error::IOError); + + Box::new(item) } - fn list>(&self, path: P) -> Box, Error = Self::Error> + Send> - where ::Metadata: Metadata + fn list>(&self, _user: &Option, path: P) -> Box, Error = Self::Error> + Send> + where >::Metadata: Metadata { // TODO: Use `?` operator here when we can use `impl Future` let full_path = match self.full_path(path) { @@ -259,7 +296,7 @@ impl StorageBackend for Filesystem { Box::new(fut.map_err(|_| Error::IOError)) } - fn get>(&self, path: P) -> Box + Send> { + fn get>(&self, _user: &Option, path: P) -> Box + Send> { let full_path = match self.full_path(path) { Ok(path) => path, Err(e) => return Box::new(future::err(e)), @@ -268,7 +305,7 @@ impl StorageBackend for Filesystem { Box::new(tokio::fs::file::File::open(full_path).map_err(|_| Error::IOError)) } - fn put, R: tokio::prelude::AsyncRead + Send + 'static>(&self, bytes: R, path: P) -> Box + Send> { + fn put, R: tokio::prelude::AsyncRead + Send + 'static>(&self, _user: &Option, bytes: R, path: P) -> Box + Send> { // TODO: Add permission checks let path = path.as_ref(); let full_path = if path.starts_with("/") { @@ -287,7 +324,7 @@ impl StorageBackend for Filesystem { Box::new(fut) } - fn del>(&self, path: P) -> Box + Send> { + fn del>(&self, _user: &Option, path: P) -> Box + Send> { let full_path = match self.full_path(path) { Ok(path) => path, Err(e) => return Box::new(future::err(e)), @@ -295,7 +332,7 @@ impl StorageBackend for Filesystem { Box::new(tokio::fs::remove_file(full_path).map_err(|_| Error::IOError)) } - fn mkd>(&self, path: P) -> Box + Send> { + fn mkd>(&self, _user: &Option, path: P) -> Box + Send> { let full_path = match self.full_path(path) { Ok(path) => path, Err(e) => return Box::new(future::err(e)), @@ -304,7 +341,7 @@ impl StorageBackend for Filesystem { Box::new(tokio::fs::create_dir(full_path).map_err(|e| {println!("error: {}", e); Error::IOError})) } - fn rename>(&self, from: P, to: P) -> Box + Send> { + fn rename>(&self, _user: &Option, from: P, to: P) -> Box + Send> { let from = match self.full_path(from) { Ok(path) => path, Err(e) => return Box::new(future::err(e)), @@ -331,6 +368,7 @@ impl StorageBackend for Filesystem { } use std::os::unix::fs::MetadataExt; + impl Metadata for std::fs::Metadata { fn len(&self) -> u64 { self.len() @@ -410,6 +448,8 @@ mod tests { use std::fs::File; use std::io::prelude::*; use pretty_assertions::assert_eq; + use crate::auth::{AnonymousUser}; + #[test] fn fs_stat() { @@ -427,7 +467,7 @@ mod tests { // Since the filesystem backend is based on futures, we need a runtime to run it let mut rt = tokio::runtime::Runtime::new().unwrap(); let filename = path.file_name().unwrap(); - let my_meta = rt.block_on(fs.stat(filename)).unwrap(); + let my_meta = rt.block_on(fs.stat(&Some(AnonymousUser{}), filename)).unwrap().metadata; assert_eq!(meta.is_dir(), my_meta.is_dir()); assert_eq!(meta.is_file(), my_meta.is_file()); @@ -450,7 +490,7 @@ mod tests { // Since the filesystem backend is based on futures, we need a runtime to run it let mut rt = tokio::runtime::Runtime::new().unwrap(); - let my_list = rt.block_on(fs.list("/").collect()).unwrap(); + let my_list = rt.block_on(fs.list(&Some(AnonymousUser{}), "/").collect()).unwrap(); assert_eq!(my_list.len(), 1); @@ -475,7 +515,7 @@ mod tests { // Since the filesystem backend is based on futures, we need a runtime to run it let mut rt = tokio::runtime::Runtime::new().unwrap(); - let my_list = rt.block_on(fs.list_fmt("/")).unwrap(); + let my_list = rt.block_on(fs.list_fmt(&Some(AnonymousUser{}), "/")).unwrap(); let my_list = std::string::String::from_utf8(my_list.into_inner()).unwrap(); @@ -498,7 +538,7 @@ mod tests { // Since the filesystem backend is based on futures, we need a runtime to run it let mut rt = tokio::runtime::Runtime::new().unwrap(); - let mut my_file = rt.block_on(fs.get(filename)).unwrap(); + let mut my_file = rt.block_on(fs.get(&Some(AnonymousUser{}), filename)).unwrap(); let mut my_content = Vec::new(); rt.block_on( future::lazy(move || { @@ -525,7 +565,7 @@ mod tests { // to completion let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(fs.put(orig_content.as_ref(), "greeting.txt")).expect("Failed to `put` file"); + rt.block_on(fs.put(&Some(AnonymousUser{}), orig_content.as_ref(), "greeting.txt")).expect("Failed to `put` file"); let mut written_content = Vec::new(); let mut f = File::open(root.join("greeting.txt")).unwrap(); @@ -566,7 +606,7 @@ mod tests { // to completion let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(fs.mkd(new_dir_name)).expect("Failed to mkd"); + rt.block_on(fs.mkd(&Some(AnonymousUser{}), new_dir_name)).expect("Failed to mkd"); let full_path = root.join(new_dir_name); let metadata = std::fs::metadata(full_path).unwrap(); @@ -585,7 +625,7 @@ mod tests { let mut rt = tokio::runtime::Runtime::new().unwrap(); let fs = Filesystem::new(&root); - rt.block_on(fs.rename(&old_filename, &new_filename)).expect("Failed to rename"); + rt.block_on(fs.rename(&Some(AnonymousUser{}), &old_filename, &new_filename)).expect("Failed to rename"); let new_full_path = root.join(new_filename); assert!(std::fs::metadata(new_full_path).expect("new filename not found").is_file());