sftp-protocol 0.1.0

A pure Rust implementation of the SFTP protocol
Documentation
use std::fmt;

use nom::IResult;
use nom::number::streaming::be_u32;
use nom::number::streaming::be_u64;
use nom::number::streaming::le_u32;
use nom::number::streaming::le_u64;

use serde::ser::Serialize;
use serde::ser::Serializer;
use serde::ser::SerializeStruct;

#[cfg(test)]
mod arbitrary;

bitflags::bitflags! {
	#[derive(Default)]
	pub struct FileAttrFlags: u32 {
		const Size = 0x00000001;
		const UidGid = 0x00000002;
		const Permissions = 0x00000004;
		const ACModTime = 0x00000008;
		const Extended = 0x80000000;
	}
}

impl<I: nom_derive::InputSlice> nom_derive::Parse<I> for FileAttrFlags {
	fn parse_be(i: I) -> IResult<I, Self> {
		let (i, flags) = be_u32(i)?;
		let this = Self::from_bits_truncate(flags);
		Ok((i, this))
	}

	fn parse_le(i: I) -> IResult<I, Self> {
		let (i, flags) = le_u32(i)?;
		let this = Self::from_bits_truncate(flags);
		Ok((i, this))
	}

	fn parse(i: I) -> IResult<I, Self> {
		Self::parse_be(i)
	}
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
#[repr(transparent)]
pub struct Permissions(u32);
impl fmt::Display for Permissions {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
		write!(f, "{}{}{}{}{}{}{}{}{}{}", self.dir(), self.owner_r(), self.owner_w(), self.owner_x(), self.group_r(), self.group_w(), self.group_x(), self.other_r(), self.other_w(), self.other_x())
	}
}

impl Permissions {
	fn dir(self) -> char /* {{{ */ {
		match self.0 & 0o40000 {
			0 => '-',
			_ => 'd'
		}
	} // }}}

	fn owner_r(self) -> char /* {{{ */ {
		match self.0 & 0o400 {
			0 => '-',
			_ => 'r'
		}
	} // }}}

	fn owner_w(self) -> char /* {{{ */ {
		match self.0 & 0o200 {
			0 => '-',
			_ => 'w'
		}
	} // }}}

	fn owner_x(self) -> char /* {{{ */ {
		match self.0 & 0o100 {
			0 => '-',
			_ => 'x'
		}
	} // }}}

	fn group_r(self) -> char /* {{{ */ {
		match self.0 & 0o040 {
			0 => '-',
			_ => 'r'
		}
	} // }}}

	fn group_w(self) -> char /* {{{ */ {
		match self.0 & 0o020 {
			0 => '-',
			_ => 'w'
		}
	} // }}}

	fn group_x(self) -> char /* {{{ */ {
		match self.0 & 0o010 {
			0 => '-',
			_ => 'x'
		}
	} // }}}

	fn other_r(self) -> char /* {{{ */ {
		match self.0 & 0o004 {
			0 => '-',
			_ => 'r'
		}
	} // }}}

	fn other_w(self) -> char /* {{{ */ {
		match self.0 & 0o002 {
			0 => '-',
			_ => 'w'
		}
	} // }}}

	fn other_x(self) -> char /* {{{ */ {
		match self.0 & 0o001 {
			0 => '-',
			_ => 'x'
		}
	} // }}}
}

impl From<u32> for Permissions {
	fn from(raw: u32) -> Self {
		Self(raw)
	}
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileAttributes {
	pub flags: FileAttrFlags,
	pub size: Option<u64>,
	pub uid: Option<u32>,
	pub gid: Option<u32>,
	pub permissions: Option<Permissions>,
	pub atime: Option<u32>,
	pub mtime: Option<u32>,
	// TODO:  Extended count, extended strings
}

fn format_month(ts: &time::OffsetDateTime) -> &'static str {
	match ts.month() {
		time::Month::January => "Jan",
		time::Month::February => "Feb",
		time::Month::March => "Mar",
		time::Month::April => "Apr",
		time::Month::May => "May",
		time::Month::June => "Jun",
		time::Month::July => "Jul",
		time::Month::August => "Aug",
		time::Month::September => "Sep",
		time::Month::October => "Oct",
		time::Month::November => "Nov",
		time::Month::December => "Dec"
	}
}

impl fmt::Display for FileAttributes {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
		match self.permissions {
			Some(v) => write!(f, "{} ", v)?,
			None => write!(f, "           ")?
			//                 1234567890
		};
		// TODO:  Actually check link count
		write!(f, "  1 ")?;
		//         123
		match self.uid {
			Some(v) => write!(f, "{: <8} ", v)?,
			None => write!(f, "         ")?
			//                 12345678
		};
		match self.gid {
			Some(v) => write!(f, "{: <8} ", v)?,
			None => write!(f, "         ")?
			//                 12345678
		};
		match self.size {
			Some(v) => write!(f, "{: >8} ", v)?,
			None => write!(f, "         ")?
			//                 12345678
		};
		match self.mtime {
			Some(v) => {
				// unwrapping is safe here because all possible u32 values are valid Unix timestamps
				let time = time::OffsetDateTime::from_unix_timestamp(v.into()).unwrap();
				write!(f, "{} {: >2} {:0>2}:{:0>2} ", format_month(&time), time.day(), time.hour(), time.minute())?
			},
			None => write!(f, "             ")?
			//                 123456789012
		};
		Ok(())
	}
}

impl Serialize for FileAttributes {
	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> /* {{{ */ {
		let mut field_count = 1;
		if(self.flags.contains(FileAttrFlags::Size)) {
			field_count += 1;
		}
		if(self.flags.contains(FileAttrFlags::UidGid)) {
			field_count += 2;
		}
		if(self.flags.contains(FileAttrFlags::Permissions)) {
			field_count += 1;
		}
		if(self.flags.contains(FileAttrFlags::ACModTime)) {
			field_count += 2;
		}
		// TODO:  Extended count and strings
		let mut state = serializer.serialize_struct("FileAttributes", field_count)?;
		state.serialize_field("flags", &self.flags.bits())?;
		if(self.flags.contains(FileAttrFlags::Size)) {
			state.serialize_field("size", &self.size.unwrap_or(0))?;
		}
		if(self.flags.contains(FileAttrFlags::UidGid)) {
			state.serialize_field("uid", &self.uid.unwrap_or(0))?;
			state.serialize_field("gid", &self.gid.unwrap_or(0))?;
		}
		if(self.flags.contains(FileAttrFlags::Permissions)) {
			state.serialize_field("permissions", &self.permissions.unwrap_or_default())?;
		}
		if(self.flags.contains(FileAttrFlags::ACModTime)) {
			state.serialize_field("atime", &self.atime.unwrap_or(0))?;
			state.serialize_field("mtime", &self.mtime.unwrap_or(0))?;
		}
		state.end()
	} // }}}
}

impl<I: nom_derive::InputSlice> nom_derive::Parse<I> for FileAttributes {
	fn parse_be(i: I) -> IResult<I, Self> /* {{{ */ {
		let (mut i, flags) = FileAttrFlags::parse(i)?;
		let mut attrs = Self{
			flags,
			..Default::default()
		};
		if(attrs.flags.contains(FileAttrFlags::Size)) {
			let (i_inner, size) = be_u64(i)?;
			i = i_inner;
			attrs.size = Some(size);
		}
		if(attrs.flags.contains(FileAttrFlags::UidGid)) {
			let (i_inner, uid) = be_u32(i)?;
			let (i_inner, gid) = be_u32(i_inner)?;
			i = i_inner;
			attrs.uid = Some(uid);
			attrs.gid = Some(gid);
		}
		if(attrs.flags.contains(FileAttrFlags::Permissions)) {
			let (i_inner, permissions) = be_u32(i)?;
			i = i_inner;
			attrs.permissions = Some(permissions.into());
		}
		if(attrs.flags.contains(FileAttrFlags::ACModTime)) {
			let (i_inner, atime) = be_u32(i)?;
			let (i_inner, mtime) = be_u32(i_inner)?;
			i = i_inner;
			attrs.atime = Some(atime);
			attrs.mtime = Some(mtime);
		}
		Ok((i, attrs))
	} // }}}

	fn parse_le(i: I) -> IResult<I, Self> /* {{{ */ {
		let mut attrs = FileAttributes::default();
		let (mut i, flags) = FileAttrFlags::parse(i)?;
		attrs.flags = flags;
		if(attrs.flags.contains(FileAttrFlags::Size)) {
			let (i_inner, size) = le_u64(i)?;
			i = i_inner;
			attrs.size = Some(size);
		}
		if(attrs.flags.contains(FileAttrFlags::UidGid)) {
			let (i_inner, uid) = le_u32(i)?;
			let (i_inner, gid) = le_u32(i_inner)?;
			i = i_inner;
			attrs.uid = Some(uid);
			attrs.gid = Some(gid);
		}
		if(attrs.flags.contains(FileAttrFlags::Permissions)) {
			let (i_inner, permissions) = le_u32(i)?;
			i = i_inner;
			attrs.permissions = Some(permissions.into());
		}
		if(attrs.flags.contains(FileAttrFlags::ACModTime)) {
			let (i_inner, atime) = le_u32(i)?;
			let (i_inner, mtime) = le_u32(i_inner)?;
			i = i_inner;
			attrs.atime = Some(atime);
			attrs.mtime = Some(mtime);
		}
		Ok((i, attrs))
	} // }}}

	fn parse(i: I) -> IResult<I, Self> {
		Self::parse_be(i)
	}
}

impl FileAttributes {
	pub fn new() -> Self /* {{{ */ {
		Self{
			flags: FileAttrFlags::from_bits_truncate(0),
			..Default::default()
		}
	} // }}}

	pub fn set_size(&mut self, size: u64) /* {{{ */ {
		self.flags.set(FileAttrFlags::Size, true);
		self.size = Some(size);
	} // }}}

	pub fn get_uid_gid(&self) -> Option<(u32, u32)> /* {{{ */ {
		match self.flags.contains(FileAttrFlags::UidGid) {
			true => Some((self.uid.unwrap(), self.gid.unwrap())),
			false => None
		}
	} // }}}

	pub fn set_uid_gid(&mut self, uid: u32, gid: u32) /* {{{ */ {
		self.flags.set(FileAttrFlags::UidGid, true);
		self.uid = Some(uid);
		self.gid = Some(gid);
	} // }}}

	pub fn get_permissions(&self) -> Option<u32> /* {{{ */ {
		self.permissions.map(|p| p.0)
	} // }}}

	pub fn set_permissions(&mut self, permissions: Permissions) /* {{{ */ {
		self.flags.set(FileAttrFlags::Permissions, true);
		self.permissions = Some(permissions);
	} // }}}

	pub fn get_atime_mtime(&self) -> Option<(u32, u32)> /* {{{ */ {
		match self.flags.contains(FileAttrFlags::ACModTime) {
			true => Some((self.atime.unwrap(), self.mtime.unwrap())),
			false => None
		}
	} // }}}

	pub fn set_atime_mtime(&mut self, atime: u32, mtime: u32) /* {{{ */ {
		self.flags.set(FileAttrFlags::ACModTime, true);
		self.atime = Some(atime);
		self.mtime = Some(mtime);
	} // }}}

	pub fn binsize(&self) -> u32 /* {{{ */ {
		let mut size = 4;
		if(self.flags.contains(FileAttrFlags::Size)) {
			size += 8;
		}
		if(self.flags.contains(FileAttrFlags::UidGid)) {
			size += 8;
		}
		if(self.flags.contains(FileAttrFlags::Permissions)) {
			size += 4;
		}
		if(self.flags.contains(FileAttrFlags::ACModTime)) {
			size += 8;
		}
		size
	} // }}}
}

impl From<&super::Metadata> for FileAttributes {
	fn from(metadata: &super::Metadata) -> Self {
		let mut this = Self::new();
		this.set_size(metadata.size);
		this.set_uid_gid(metadata.uid, metadata.gid);
		this.set_permissions({
			let mut permissions = metadata.permissions;
			if(metadata.is_dir) {
				permissions |= 0o40000;
			}
			permissions.into()
		});
		this.set_atime_mtime(metadata.atime.unix_timestamp() as u32, metadata.mtime.unix_timestamp() as u32);
		this
	}
}

impl From<super::Metadata> for FileAttributes {
	fn from(metadata: super::Metadata) -> Self {
		Self::from(&metadata)
	}
}