openssh_sftp_protocol/
response.rs

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    /// The extension that the sftp-server supports.
19    #[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    /// * `bytes` - should not include the initial 4-byte which server
40    ///   as the length of the whole packet.
41    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            // sftp v3 does not specify the encoding of extension names and revisions.
58            //
59            // Read both name and revision before continue parsing them
60            // so that if the current iteration is skipped by 'continue',
61            // the next iteration can continue read in extensions without error.
62            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/// Payload of extended reply response when [`crate::request::RequestInner::Limits`]
120/// is sent.
121#[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    /// Return true if the response is a data response.
152    pub fn is_data(packet_type: u8) -> bool {
153        packet_type == constants::SSH_FXP_DATA
154    }
155
156    /// Return true if the response is a extended reply response.
157    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    /// Indicates end-of-file condition.
223    ///
224    /// For RequestInner::Read it means that no more data is available in the file,
225    /// and for RequestInner::Readdir it indicates that no more files are contained
226    /// in the directory.
227    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/// Entry in [`ResponseInner::Name`]
257#[derive(Debug, Clone)]
258pub struct NameEntry {
259    pub filename: Box<Path>,
260
261    pub attrs: FileAttrs,
262}