use std::io::{Read, Write};
use super::packet::{read_packet, write_packet, Packet};
use super::types::{Attrs, FxpStatus, NameEntry, SftpError, SFTP_VERSION};
pub struct SftpClient<T: Read + Write> {
transport: T,
next_id: u32,
extensions: Vec<(Vec<u8>, Vec<u8>)>,
server_version: u32,
}
impl<T: Read + Write> SftpClient<T> {
pub fn new(mut transport: T) -> Result<Self, SftpError> {
write_packet(
&mut transport,
&Packet::Init {
version: SFTP_VERSION,
extensions: vec![],
},
)?;
let body = read_packet(&mut transport)?;
let (server_version, extensions) = match Packet::decode(&body)? {
Packet::Version {
version,
extensions,
} => {
if version < 3 {
return Err(SftpError::Protocol("sftp: server version < 3"));
}
(version, extensions)
}
_ => return Err(SftpError::Protocol("sftp: expected VERSION")),
};
Ok(Self {
transport,
next_id: 1,
extensions,
server_version,
})
}
pub fn server_version(&self) -> u32 {
self.server_version
}
pub fn extensions(&self) -> &[(Vec<u8>, Vec<u8>)] {
&self.extensions
}
fn next_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id = self.next_id.wrapping_add(1).max(1);
id
}
fn request(&mut self, mut pkt: Packet) -> Result<Packet, SftpError> {
let id = self.next_id();
set_id(&mut pkt, id);
write_packet(&mut self.transport, &pkt)?;
let body = read_packet(&mut self.transport)?;
let resp = Packet::decode(&body)?;
match resp.id() {
Some(rid) if rid == id => Ok(resp),
_ => Err(SftpError::Protocol("sftp: response id mismatch")),
}
}
pub fn open(&mut self, path: &[u8], pflags: u32, attrs: Attrs) -> Result<Vec<u8>, SftpError> {
let resp = self.request(Packet::Open {
id: 0,
path: path.to_vec(),
pflags,
attrs,
})?;
expect_handle(resp)
}
pub fn close(&mut self, handle: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Close {
id: 0,
handle: handle.to_vec(),
})?;
expect_ok(resp)
}
pub fn read(&mut self, handle: &[u8], offset: u64, len: u32) -> Result<Vec<u8>, SftpError> {
let resp = self.request(Packet::Read {
id: 0,
handle: handle.to_vec(),
offset,
len,
})?;
match resp {
Packet::Data { data, .. } => Ok(data),
Packet::Status {
code: FxpStatus::Eof,
..
} => Ok(Vec::new()),
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected DATA or STATUS")),
}
}
pub fn write(&mut self, handle: &[u8], offset: u64, data: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Write {
id: 0,
handle: handle.to_vec(),
offset,
data: data.to_vec(),
})?;
expect_ok(resp)
}
pub fn stat(&mut self, path: &[u8]) -> Result<Attrs, SftpError> {
self.stat_inner(Packet::Stat {
id: 0,
path: path.to_vec(),
})
}
pub fn lstat(&mut self, path: &[u8]) -> Result<Attrs, SftpError> {
self.stat_inner(Packet::Lstat {
id: 0,
path: path.to_vec(),
})
}
pub fn fstat(&mut self, handle: &[u8]) -> Result<Attrs, SftpError> {
self.stat_inner(Packet::Fstat {
id: 0,
handle: handle.to_vec(),
})
}
fn stat_inner(&mut self, pkt: Packet) -> Result<Attrs, SftpError> {
let resp = self.request(pkt)?;
match resp {
Packet::Attrs { attrs, .. } => Ok(attrs),
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected ATTRS")),
}
}
pub fn setstat(&mut self, path: &[u8], attrs: Attrs) -> Result<(), SftpError> {
let resp = self.request(Packet::Setstat {
id: 0,
path: path.to_vec(),
attrs,
})?;
expect_ok(resp)
}
pub fn fsetstat(&mut self, handle: &[u8], attrs: Attrs) -> Result<(), SftpError> {
let resp = self.request(Packet::Fsetstat {
id: 0,
handle: handle.to_vec(),
attrs,
})?;
expect_ok(resp)
}
pub fn opendir(&mut self, path: &[u8]) -> Result<Vec<u8>, SftpError> {
let resp = self.request(Packet::Opendir {
id: 0,
path: path.to_vec(),
})?;
expect_handle(resp)
}
pub fn readdir(&mut self, handle: &[u8]) -> Result<Option<Vec<NameEntry>>, SftpError> {
let resp = self.request(Packet::Readdir {
id: 0,
handle: handle.to_vec(),
})?;
match resp {
Packet::Name { entries, .. } => Ok(Some(entries)),
Packet::Status {
code: FxpStatus::Eof,
..
} => Ok(None),
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected NAME or STATUS")),
}
}
pub fn mkdir(&mut self, path: &[u8], attrs: Attrs) -> Result<(), SftpError> {
let resp = self.request(Packet::Mkdir {
id: 0,
path: path.to_vec(),
attrs,
})?;
expect_ok(resp)
}
pub fn rmdir(&mut self, path: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Rmdir {
id: 0,
path: path.to_vec(),
})?;
expect_ok(resp)
}
pub fn remove(&mut self, path: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Remove {
id: 0,
path: path.to_vec(),
})?;
expect_ok(resp)
}
pub fn rename(&mut self, oldpath: &[u8], newpath: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Rename {
id: 0,
oldpath: oldpath.to_vec(),
newpath: newpath.to_vec(),
})?;
expect_ok(resp)
}
pub fn realpath(&mut self, path: &[u8]) -> Result<Vec<u8>, SftpError> {
let resp = self.request(Packet::Realpath {
id: 0,
path: path.to_vec(),
})?;
match resp {
Packet::Name { mut entries, .. } if !entries.is_empty() => {
Ok(entries.remove(0).filename)
}
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected NAME")),
}
}
pub fn symlink(&mut self, target_path: &[u8], link_path: &[u8]) -> Result<(), SftpError> {
let resp = self.request(Packet::Symlink {
id: 0,
target_path: target_path.to_vec(),
link_path: link_path.to_vec(),
})?;
expect_ok(resp)
}
pub fn readlink(&mut self, path: &[u8]) -> Result<Vec<u8>, SftpError> {
let resp = self.request(Packet::Readlink {
id: 0,
path: path.to_vec(),
})?;
match resp {
Packet::Name { mut entries, .. } if !entries.is_empty() => {
Ok(entries.remove(0).filename)
}
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected NAME")),
}
}
}
fn expect_handle(p: Packet) -> Result<Vec<u8>, SftpError> {
match p {
Packet::Handle { handle, .. } => Ok(handle),
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected HANDLE")),
}
}
fn expect_ok(p: Packet) -> Result<(), SftpError> {
match p {
Packet::Status {
code: FxpStatus::Ok,
..
} => Ok(()),
Packet::Status { code, message, .. } => Err(SftpError::Status {
code,
message: String::from_utf8_lossy(&message).into_owned(),
}),
_ => Err(SftpError::Protocol("sftp: expected STATUS")),
}
}
fn set_id(pkt: &mut Packet, new_id: u32) {
match pkt {
Packet::Open { id, .. }
| Packet::Close { id, .. }
| Packet::Read { id, .. }
| Packet::Write { id, .. }
| Packet::Lstat { id, .. }
| Packet::Fstat { id, .. }
| Packet::Setstat { id, .. }
| Packet::Fsetstat { id, .. }
| Packet::Opendir { id, .. }
| Packet::Readdir { id, .. }
| Packet::Remove { id, .. }
| Packet::Mkdir { id, .. }
| Packet::Rmdir { id, .. }
| Packet::Realpath { id, .. }
| Packet::Stat { id, .. }
| Packet::Rename { id, .. }
| Packet::Readlink { id, .. }
| Packet::Symlink { id, .. }
| Packet::Extended { id, .. } => *id = new_id,
_ => {}
}
}