ironrdp_cliprdr/pdu/
file_contents.rs

1use std::borrow::Cow;
2
3use bitflags::bitflags;
4use ironrdp_core::{
5    cast_int, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor,
6    WriteCursor,
7};
8use ironrdp_pdu::impl_pdu_borrowing;
9use ironrdp_pdu::utils::{combine_u64, split_u64};
10
11use crate::pdu::{ClipboardPduFlags, PartialHeader};
12
13bitflags! {
14    /// Represents `dwFlags` field of `CLIPRDR_FILECONTENTS_REQUEST` structure.
15    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16    pub struct FileContentsFlags: u32 {
17        /// A request for the size of the file identified by the lindex field. The size MUST be
18        /// returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to
19        /// 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be
20        /// set to 0x00000000.
21        const SIZE = 0x0000_0001;
22        /// A request for the data present in the file identified by the lindex field. The data
23        /// to be retrieved is extracted starting from the offset given by the nPositionLow
24        /// and nPositionHigh fields. The maximum number of bytes to extract is specified
25        /// by the cbRequested field.
26        const DATA = 0x0000_0002;
27    }
28}
29
30/// Represents `CLIPRDR_FILECONTENTS_RESPONSE`
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct FileContentsResponse<'a> {
33    is_error: bool,
34    stream_id: u32,
35    data: Cow<'a, [u8]>,
36}
37
38impl_pdu_borrowing!(FileContentsResponse<'_>, OwnedFileContentsResponse);
39
40impl IntoOwned for FileContentsResponse<'_> {
41    type Owned = OwnedFileContentsResponse;
42
43    fn into_owned(self) -> Self::Owned {
44        OwnedFileContentsResponse {
45            is_error: self.is_error,
46            stream_id: self.stream_id,
47            data: Cow::Owned(self.data.into_owned()),
48        }
49    }
50}
51
52impl<'a> FileContentsResponse<'a> {
53    const NAME: &'static str = "CLIPRDR_FILECONTENTS_RESPONSE";
54    const FIXED_PART_SIZE: usize = 4 /* streamId */;
55
56    fn inner_size(&self) -> usize {
57        Self::FIXED_PART_SIZE + self.data.len()
58    }
59
60    /// Creates a new `FileContentsResponse` with u64 size value
61    pub fn new_size_response(stream_id: u32, size: u64) -> Self {
62        Self {
63            is_error: false,
64            stream_id,
65            data: Cow::Owned(size.to_le_bytes().to_vec()),
66        }
67    }
68
69    /// Creates a new `FileContentsResponse` with file contents value
70    pub fn new_data_response(stream_id: u32, data: impl Into<Cow<'a, [u8]>>) -> Self {
71        Self {
72            is_error: false,
73            stream_id,
74            data: data.into(),
75        }
76    }
77
78    /// Creates new `FileContentsResponse` with error
79    pub fn new_error(stream_id: u32) -> Self {
80        Self {
81            is_error: true,
82            stream_id,
83            data: Cow::Borrowed(&[]),
84        }
85    }
86
87    pub fn stream_id(&self) -> u32 {
88        self.stream_id
89    }
90
91    pub fn data(&self) -> &[u8] {
92        &self.data
93    }
94
95    /// Read data as u64 size value
96    pub fn data_as_size(&self) -> DecodeResult<u64> {
97        if self.data.len() != 8 {
98            return Err(invalid_field_err!(
99                "requestedFileContentsData",
100                "Invalid data size for u64 size"
101            ));
102        }
103
104        Ok(u64::from_le_bytes(self.data.as_ref().try_into().unwrap()))
105    }
106}
107
108impl Encode for FileContentsResponse<'_> {
109    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
110        let flags = if self.is_error {
111            ClipboardPduFlags::RESPONSE_FAIL
112        } else {
113            ClipboardPduFlags::RESPONSE_OK
114        };
115
116        let header = PartialHeader::new_with_flags(cast_int!("dataLen", self.inner_size())?, flags);
117        header.encode(dst)?;
118
119        ensure_size!(in: dst, size: self.inner_size());
120
121        dst.write_u32(self.stream_id);
122        dst.write_slice(&self.data);
123
124        Ok(())
125    }
126
127    fn name(&self) -> &'static str {
128        Self::NAME
129    }
130
131    fn size(&self) -> usize {
132        PartialHeader::SIZE + self.inner_size()
133    }
134}
135
136impl<'de> Decode<'de> for FileContentsResponse<'de> {
137    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
138        let header = PartialHeader::decode(src)?;
139
140        let is_error = header.message_flags.contains(ClipboardPduFlags::RESPONSE_FAIL);
141
142        ensure_size!(in: src, size: header.data_length());
143
144        if header.data_length() < Self::FIXED_PART_SIZE {
145            return Err(invalid_field_err!("requestedFileContentsData", "Invalid data size"));
146        };
147
148        let data_size = header.data_length() - Self::FIXED_PART_SIZE;
149
150        let stream_id = src.read_u32();
151        let data = src.read_slice(data_size);
152
153        Ok(Self {
154            is_error,
155            stream_id,
156            data: Cow::Borrowed(data),
157        })
158    }
159}
160
161/// Represents `CLIPRDR_FILECONTENTS_REQUEST`
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct FileContentsRequest {
164    pub stream_id: u32,
165    pub index: u32,
166    pub flags: FileContentsFlags,
167    pub position: u64,
168    pub requested_size: u32,
169    pub data_id: Option<u32>,
170}
171
172impl FileContentsRequest {
173    const NAME: &'static str = "CLIPRDR_FILECONTENTS_REQUEST";
174    const FIXED_PART_SIZE: usize = 4 /* streamId */ + 4 /* idx */ + 4 /* flags */ + 8 /* position */ + 4 /* reqSize */;
175
176    fn inner_size(&self) -> usize {
177        let data_id_size = match self.data_id {
178            Some(_) => 4,
179            None => 0,
180        };
181
182        Self::FIXED_PART_SIZE + data_id_size
183    }
184}
185
186impl Encode for FileContentsRequest {
187    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
188        let header = PartialHeader::new(cast_int!("dataLen", self.inner_size())?);
189        header.encode(dst)?;
190
191        ensure_size!(in: dst, size: self.inner_size());
192
193        dst.write_u32(self.stream_id);
194        dst.write_u32(self.index);
195        dst.write_u32(self.flags.bits());
196
197        let (position_lo, position_hi) = split_u64(self.position);
198        dst.write_u32(position_lo);
199        dst.write_u32(position_hi);
200        dst.write_u32(self.requested_size);
201
202        if let Some(data_id) = self.data_id {
203            dst.write_u32(data_id);
204        };
205
206        Ok(())
207    }
208
209    fn name(&self) -> &'static str {
210        Self::NAME
211    }
212
213    fn size(&self) -> usize {
214        PartialHeader::SIZE + self.inner_size()
215    }
216}
217
218impl<'de> Decode<'de> for FileContentsRequest {
219    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
220        let header = PartialHeader::decode(src)?;
221
222        let read_data_id = header.data_length() > Self::FIXED_PART_SIZE;
223
224        let mut expected_size = Self::FIXED_PART_SIZE;
225        if read_data_id {
226            expected_size += 4;
227        }
228
229        ensure_size!(in: src, size: expected_size);
230
231        let stream_id = src.read_u32();
232        let index = src.read_u32();
233        let flags = FileContentsFlags::from_bits_truncate(src.read_u32());
234        let position_lo = src.read_u32();
235        let position_hi = src.read_u32();
236        let position = combine_u64(position_lo, position_hi);
237        let requested_size = src.read_u32();
238        let data_id = if read_data_id { Some(src.read_u32()) } else { None };
239
240        Ok(Self {
241            stream_id,
242            index,
243            flags,
244            position,
245            requested_size,
246            data_id,
247        })
248    }
249}