1#![forbid(unsafe_code)]
2
3use super::{
4 file_attrs::FileAttrs,
5 {constants, seq_iter::SeqIter, visitor::impl_visitor, HandleOwned},
6};
7
8use std::{borrow::Cow, iter::FusedIterator, path::Path, str::from_utf8};
9
10use bitflags::bitflags;
11use openssh_sftp_protocol_error::{ErrMsg, ErrorCode};
12use serde::{
13 de::{Deserializer, Error, Unexpected},
14 Deserialize,
15};
16
17bitflags! {
18 #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
20 pub struct Extensions: u16 {
21 const POSIX_RENAME = 1 << 0;
22 const STATVFS = 1 << 1;
23 const FSTATVFS= 1<< 2;
24 const HARDLINK= 1<< 3;
25 const FSYNC= 1<< 4;
26 const LSETSTAT= 1<< 5;
27 const LIMITS= 1<< 6;
28 const EXPAND_PATH= 1<< 7;
29 const COPY_DATA= 1<< 8;
30 }
31}
32
33#[derive(Debug, Clone, Copy)]
34pub struct ServerVersion {
35 pub version: u32,
36 pub extensions: Extensions,
37}
38impl ServerVersion {
39 pub fn deserialize<'de, It>(
42 de: &mut ssh_format::Deserializer<'de, It>,
43 ) -> ssh_format::Result<Self>
44 where
45 It: FusedIterator + Iterator<Item = &'de [u8]>,
46 {
47 let packet_type = u8::deserialize(&mut *de)?;
48 if packet_type != constants::SSH_FXP_VERSION {
49 return Err(ssh_format::Error::custom("Unexpected response"));
50 }
51
52 let version = u32::deserialize(&mut *de)?;
53
54 let mut extensions = Extensions::default();
55
56 while de.has_remaining_data() {
57 let name = Cow::<'_, [u8]>::deserialize(&mut *de)?;
63 let revision = Cow::<'_, [u8]>::deserialize(&mut *de)?;
64
65 let optional_extension_pair = (|| {
66 let name = from_utf8(&name).ok()?;
67 let revision = from_utf8(&revision).ok()?;
68 let revision: u64 = revision.parse().ok()?;
69
70 Some((name, revision))
71 })();
72
73 let extension_pair = if let Some(extension_pair) = optional_extension_pair {
74 extension_pair
75 } else {
76 continue;
77 };
78
79 match extension_pair {
80 constants::EXT_NAME_POSIX_RENAME => {
81 extensions |= Extensions::POSIX_RENAME;
82 }
83 constants::EXT_NAME_STATVFS => {
84 extensions |= Extensions::STATVFS;
85 }
86 constants::EXT_NAME_FSTATVFS => {
87 extensions |= Extensions::FSTATVFS;
88 }
89 constants::EXT_NAME_HARDLINK => {
90 extensions |= Extensions::HARDLINK;
91 }
92 constants::EXT_NAME_FSYNC => {
93 extensions |= Extensions::FSYNC;
94 }
95 constants::EXT_NAME_LSETSTAT => {
96 extensions |= Extensions::LSETSTAT;
97 }
98 constants::EXT_NAME_LIMITS => {
99 extensions |= Extensions::LIMITS;
100 }
101 constants::EXT_NAME_EXPAND_PATH => {
102 extensions |= Extensions::EXPAND_PATH;
103 }
104 constants::EXT_NAME_COPY_DATA => {
105 extensions |= Extensions::COPY_DATA;
106 }
107
108 _ => (),
109 }
110 }
111
112 Ok(Self {
113 version,
114 extensions,
115 })
116 }
117}
118
119#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize)]
122pub struct Limits {
123 pub packet_len: u64,
124 pub read_len: u64,
125 pub write_len: u64,
126 pub open_handles: u64,
127}
128
129#[derive(Debug)]
130pub enum ResponseInner {
131 Status {
132 status_code: StatusCode,
133
134 err_msg: ErrMsg,
135 },
136
137 Handle(HandleOwned),
138
139 Name(Box<[NameEntry]>),
140
141 Attrs(FileAttrs),
142}
143
144#[derive(Debug)]
145pub struct Response {
146 pub response_id: u32,
147 pub response_inner: ResponseInner,
148}
149
150impl Response {
151 pub fn is_data(packet_type: u8) -> bool {
153 packet_type == constants::SSH_FXP_DATA
154 }
155
156 pub fn is_extended_reply(packet_type: u8) -> bool {
158 packet_type == constants::SSH_FXP_EXTENDED_REPLY
159 }
160}
161
162impl_visitor!(
163 Response,
164 ResponseVisitor,
165 "Expects a u8 type and payload",
166 seq,
167 {
168 use constants::*;
169 use ResponseInner::*;
170
171 let mut iter = SeqIter::new(seq);
172
173 let discriminant: u8 = iter.get_next()?;
174 let response_id: u32 = iter.get_next()?;
175
176 let response_inner = match discriminant {
177 SSH_FXP_STATUS => Status {
178 status_code: iter.get_next()?,
179 err_msg: iter.get_next()?,
180 },
181
182 SSH_FXP_HANDLE => Handle(HandleOwned(iter.get_next()?)),
183
184 SSH_FXP_NAME => {
185 let len: u32 = iter.get_next()?;
186 let len = len as usize;
187 let mut entries = Vec::<NameEntry>::with_capacity(len);
188
189 for _ in 0..len {
190 let filename: Box<Path> = iter.get_next()?;
191 let _longname: &[u8] = iter.get_next()?;
192 let attrs: FileAttrs = iter.get_next()?;
193
194 entries.push(NameEntry { filename, attrs });
195 }
196
197 Name(entries.into_boxed_slice())
198 }
199
200 SSH_FXP_ATTRS => Attrs(iter.get_next()?),
201
202 _ => {
203 return Err(Error::invalid_value(
204 Unexpected::Unsigned(discriminant as u64),
205 &"Invalid packet type",
206 ))
207 }
208 };
209
210 Ok(Response {
211 response_id,
212 response_inner,
213 })
214 }
215);
216
217#[derive(Debug, Copy, Clone)]
218pub enum StatusCode {
219 Success,
220 Failure(ErrorCode),
221
222 Eof,
228}
229impl<'de> Deserialize<'de> for StatusCode {
230 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
231 use constants::*;
232 use ErrorCode::*;
233
234 let discriminant = <u32 as Deserialize>::deserialize(deserializer)?;
235
236 match discriminant {
237 SSH_FX_OK => Ok(StatusCode::Success),
238 SSH_FX_EOF => Ok(StatusCode::Eof),
239 SSH_FX_NO_SUCH_FILE => Ok(StatusCode::Failure(NoSuchFile)),
240 SSH_FX_PERMISSION_DENIED => Ok(StatusCode::Failure(PermDenied)),
241 SSH_FX_FAILURE => Ok(StatusCode::Failure(Failure)),
242 SSH_FX_BAD_MESSAGE => Ok(StatusCode::Failure(BadMessage)),
243 SSH_FX_OP_UNSUPPORTED => Ok(StatusCode::Failure(OpUnsupported)),
244
245 SSH_FX_NO_CONNECTION | SSH_FX_CONNECTION_LOST => Err(Error::invalid_value(
246 Unexpected::Unsigned(discriminant as u64),
247 &"Server MUST NOT return SSH_FX_NO_CONNECTION or SSH_FX_CONNECTION_LOST \
248 for they are pseudo-error that can only be generated locally.",
249 )),
250
251 _ => Ok(StatusCode::Failure(Unknown)),
252 }
253 }
254}
255
256#[derive(Debug, Clone)]
258pub struct NameEntry {
259 pub filename: Box<Path>,
260
261 pub attrs: FileAttrs,
262}