ironrdp_cliprdr/pdu/
format_list.rs

1use std::borrow::Cow;
2
3use ironrdp_core::{
4    cast_int, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor,
5    WriteCursor,
6};
7use ironrdp_pdu::utils::{read_string_from_cursor, to_utf16_bytes, write_string_to_cursor, CharacterSet};
8use ironrdp_pdu::{decode_err, impl_pdu_borrowing, impl_pdu_pod, PduResult};
9
10use crate::pdu::{ClipboardPduFlags, PartialHeader};
11
12/// Clipboard format id.
13///
14/// [Standard clipboard formats](https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats)
15/// defined by Microsoft are available as constants.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct ClipboardFormatId(pub u32);
18
19impl ClipboardFormatId {
20    /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination.
21    /// A null character signals the end of the data. Use this format for ANSI text.
22    pub const CF_TEXT: Self = Self(1);
23
24    /// A handle to a bitmap (HBITMAP).
25    pub const CF_BITMAP: Self = Self(2);
26
27    /// Handle to a metafile picture format as defined by the METAFILEPICT structure.
28    ///
29    /// When passing a CF_METAFILEPICT handle by means of DDE, the application responsible for
30    /// deleting hMem should also free the metafile referred to by the CF_METAFILEPICT handle.
31    pub const CF_METAFILEPICT: Self = Self(3);
32
33    /// Microsoft Symbolic Link (SYLK) format.
34    pub const CF_SYLK: Self = Self(4);
35
36    /// Software Arts' Data Interchange Format.
37    pub const CF_DIF: Self = Self(5);
38
39    /// Tagged-image file format.
40    pub const CF_TIFF: Self = Self(6);
41
42    /// Text format containing characters in the OEM character set. Each line ends with a carriage
43    /// return/linefeed (CR-LF) combination. A null character signals the end of the data.
44    pub const CF_OEMTEXT: Self = Self(7);
45
46    /// A memory object containing a BITMAPINFO structure followed by the bitmap bits.
47    pub const CF_DIB: Self = Self(8);
48
49    /// Handle to a color palette.
50    ///
51    /// Whenever an application places data in the clipboard that
52    /// depends on or assumes a color palette, it should place the palette on the clipboard as well.
53    /// If the clipboard contains data in the CF_PALETTE (logical color palette) format, the
54    /// application should use the SelectPalette and RealizePalette functions to realize (compare)
55    /// any other data in the clipboard against that logical palette. When displaying clipboard
56    /// data, the clipboard always uses as its current palette any object on the clipboard that is
57    /// in the CF_PALETTE format.
58    ///
59    /// NOTE: When transferred over `CLIPRDR`, [`crate::pdu::format_data::ClipboardPalette`] structure
60    /// is used instead of `HPALETTE`.
61    pub const CF_PALETTE: Self = Self(9);
62
63    /// Data for the pen extensions to the Microsoft Windows for Pen Computing.
64    pub const CF_PENDATA: Self = Self(10);
65
66    /// Represents audio data more complex than can be represented in a CF_WAVE standard wave format.
67    pub const CF_RIFF: Self = Self(11);
68
69    /// Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM.
70    pub const CF_WAVE: Self = Self(12);
71
72    /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination.
73    /// A null character signals the end of the data.
74    pub const CF_UNICODETEXT: Self = Self(13);
75
76    /// A handle to an enhanced metafile (HENHMETAFILE).
77    ///
78    /// NOTE: When transferred over `CLIPRDR`, [`crate::pdu::format_data::PackedMetafile`] structure
79    /// is used instead of `HENHMETAFILE`.
80    pub const CF_ENHMETAFILE: Self = Self(14);
81
82    /// A handle to type HDROP that identifies a list of files. An application can retrieve
83    /// information about the files by passing the handle to the DragQueryFile function.
84    pub const CF_HDROP: Self = Self(15);
85
86    /// The data is a handle (HGLOBAL) to the locale identifier (LCID) associated with text in the
87    /// clipboard.
88    ///
89    /// When you close the clipboard, if it contains CF_TEXT data but no CF_LOCALE data,
90    /// the system automatically sets the CF_LOCALE format to the current input language. You can
91    /// use the CF_LOCALE format to associate a different locale with the clipboard text. An
92    /// application that pastes text from the clipboard can retrieve this format to determine which
93    /// character set was used to generate the text. Note that the clipboard does not support plain
94    /// text in multiple character sets. To achieve this, use a formatted text data type such as
95    /// RTF instead.The system uses the code page associated with CF_LOCALE to implicitly convert
96    /// from CF_TEXT to CF_UNICODETEXT. Therefore, the correct code page table is used for the
97    /// conversion.
98    pub const CF_LOCALE: Self = Self(16);
99
100    /// A memory object containing a BITMAPV5HEADER structure followed by the bitmap color space
101    /// information and the bitmap bits.
102    pub const CF_DIBV5: Self = Self(17);
103
104    /// Creates new `ClipboardFormatId` with given id. Note that [`ClipboardFormatId`] already
105    /// defines constants for standard clipboard formats, [`Self::new`] should only be
106    /// used for custom/OS-specific formats.
107    pub fn new(id: u32) -> Self {
108        Self(id)
109    }
110
111    pub fn value(&self) -> u32 {
112        self.0
113    }
114
115    pub fn is_standard(self) -> bool {
116        matches!(
117            self,
118            Self::CF_TEXT
119                | Self::CF_BITMAP
120                | Self::CF_METAFILEPICT
121                | Self::CF_SYLK
122                | Self::CF_DIF
123                | Self::CF_TIFF
124                | Self::CF_OEMTEXT
125                | Self::CF_DIB
126                | Self::CF_PALETTE
127                | Self::CF_PENDATA
128                | Self::CF_RIFF
129                | Self::CF_WAVE
130                | Self::CF_UNICODETEXT
131                | Self::CF_ENHMETAFILE
132                | Self::CF_HDROP
133                | Self::CF_LOCALE
134                | Self::CF_DIBV5
135        )
136    }
137
138    pub fn is_registered(self) -> bool {
139        (self.0 >= 0xC000) && (self.0 <= 0xFFFF)
140    }
141}
142
143/// Clipboard format name. Hardcoded format names defined by [MS-RDPECLIP] are available as
144/// constants.
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ClipboardFormatName(Cow<'static, str>);
147
148impl ClipboardFormatName {
149    /// Special format name for file lists defined by [`MS-RDPECLIP`] which is used for clipboard
150    /// data  with [`crate::pdu::format_data::PackedFileList`] payload.
151    pub const FILE_LIST: Self = Self::new_static("FileGroupDescriptorW");
152
153    /// Special format defined by Windows to store HTML fragment in clipboard.
154    pub const HTML: Self = Self::new_static("HTML Format");
155
156    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
157        Self(name.into())
158    }
159
160    /// Same as [`Self::new`], but for `'static` string - it can be used in const contexts.
161    pub const fn new_static(name: &'static str) -> Self {
162        Self(Cow::Borrowed(name))
163    }
164
165    pub fn value(&self) -> &str {
166        &self.0
167    }
168}
169
170/// Represents `CLIPRDR_SHORT_FORMAT_NAME` and `CLIPRDR_LONG_FORMAT_NAME`
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ClipboardFormat {
173    pub id: ClipboardFormatId,
174    pub name: Option<ClipboardFormatName>,
175}
176
177impl ClipboardFormat {
178    /// Creates unnamed `ClipboardFormat` with given id.
179    pub const fn new(id: ClipboardFormatId) -> Self {
180        Self { id, name: None }
181    }
182
183    /// Sets clipboard format name.
184    ///
185    /// This is typically used for custom/OS-specific formats where a name must be associated to
186    /// the `ClipboardFormatId` in order to distinguish between vendors.
187    #[must_use]
188    pub fn with_name(self, name: ClipboardFormatName) -> Self {
189        if name.0.is_empty() {
190            return Self {
191                id: self.id,
192                name: None,
193            };
194        }
195
196        Self {
197            id: self.id,
198            name: Some(name),
199        }
200    }
201
202    #[inline]
203    pub fn id(&self) -> ClipboardFormatId {
204        self.id
205    }
206
207    #[inline]
208    pub fn name(&self) -> Option<&ClipboardFormatName> {
209        self.name.as_ref()
210    }
211}
212
213/// Represents `CLIPRDR_FORMAT_LIST`
214#[derive(Debug, Clone, PartialEq, Eq)]
215pub struct FormatList<'a> {
216    use_ascii: bool,
217    encoded_formats: Cow<'a, [u8]>,
218}
219
220impl_pdu_borrowing!(FormatList<'_>, OwnedFormatList);
221
222impl IntoOwned for FormatList<'_> {
223    type Owned = OwnedFormatList;
224
225    fn into_owned(self) -> Self::Owned {
226        OwnedFormatList {
227            use_ascii: self.use_ascii,
228            encoded_formats: Cow::Owned(self.encoded_formats.into_owned()),
229        }
230    }
231}
232
233impl FormatList<'_> {
234    const NAME: &'static str = "CLIPRDR_FORMAT_LIST";
235
236    // `CLIPRDR_SHORT_FORMAT_NAME` size
237    const SHORT_FORMAT_SIZE: usize = 4 /* formatId */ + 32 /* name */;
238
239    fn new_impl(formats: &[ClipboardFormat], use_long_format: bool, use_ascii: bool) -> EncodeResult<Self> {
240        let charset = if use_ascii {
241            CharacterSet::Ansi
242        } else {
243            CharacterSet::Unicode
244        };
245
246        let mut bytes_written = 0;
247
248        if use_long_format {
249            // Sane default for formats buffer size to avoid reallocations
250            const DEFAULT_STRING_BUFFER_SIZE: usize = 1024;
251            let mut buffer = vec![0u8; DEFAULT_STRING_BUFFER_SIZE];
252
253            for format in formats {
254                let encoded_string = match charset {
255                    CharacterSet::Ansi => {
256                        let mut str_buffer = format
257                            .name
258                            .as_ref()
259                            .map(|name| name.value().as_bytes().to_vec())
260                            .unwrap_or_default();
261                        str_buffer.push(b'\0');
262                        str_buffer
263                    }
264                    CharacterSet::Unicode => {
265                        let mut str_buffer = format
266                            .name
267                            .as_ref()
268                            .map(|name| to_utf16_bytes(name.value()))
269                            .unwrap_or_default();
270                        str_buffer.push(b'\0');
271                        str_buffer.push(b'\0');
272                        str_buffer
273                    }
274                };
275
276                let required_size = 4 + encoded_string.len();
277                if buffer.len() - bytes_written < required_size {
278                    buffer.resize(bytes_written + required_size, 0);
279                }
280
281                let mut cursor = WriteCursor::new(&mut buffer[bytes_written..]);
282
283                // Write will never fail, as we pre-allocated space in buffer
284                cursor.write_u32(format.id.value());
285                cursor.write_slice(&encoded_string);
286
287                bytes_written += required_size;
288            }
289
290            buffer.truncate(bytes_written);
291
292            Ok(Self {
293                use_ascii,
294                encoded_formats: Cow::Owned(buffer),
295            })
296        } else {
297            let mut buffer = vec![0u8; Self::SHORT_FORMAT_SIZE * formats.len()];
298            for (idx, format) in formats.iter().enumerate() {
299                let mut cursor = WriteCursor::new(&mut buffer[idx * Self::SHORT_FORMAT_SIZE..]);
300                cursor.write_u32(format.id.value());
301                write_string_to_cursor(
302                    &mut cursor,
303                    format.name.as_ref().map(|name| name.value()).unwrap_or_default(),
304                    charset,
305                    true,
306                )?;
307            }
308
309            Ok(Self {
310                use_ascii,
311                encoded_formats: Cow::Owned(buffer),
312            })
313        }
314    }
315
316    pub fn new_unicode(formats: &[ClipboardFormat], use_long_format: bool) -> EncodeResult<Self> {
317        Self::new_impl(formats, use_long_format, false)
318    }
319
320    pub fn new_ascii(formats: &[ClipboardFormat], use_long_format: bool) -> EncodeResult<Self> {
321        Self::new_impl(formats, use_long_format, true)
322    }
323
324    pub fn get_formats(&self, use_long_format: bool) -> PduResult<Vec<ClipboardFormat>> {
325        let mut src = ReadCursor::new(self.encoded_formats.as_ref());
326        let charset = if self.use_ascii {
327            CharacterSet::Ansi
328        } else {
329            CharacterSet::Unicode
330        };
331
332        if use_long_format {
333            // Minimal `CLIPRDR_LONG_FORMAT_NAME` size (id + null-terminated name)
334            const MINIMAL_FORMAT_SIZE: usize = 4 /* id */ + 2 /* null-terminated name */;
335
336            let mut formats = Vec::with_capacity(16);
337
338            while src.len() >= MINIMAL_FORMAT_SIZE {
339                let id = src.read_u32();
340                let name = read_string_from_cursor(&mut src, charset, true).map_err(|e| decode_err!(e))?;
341
342                let format = ClipboardFormat::new(ClipboardFormatId::new(id)).with_name(ClipboardFormatName::new(name));
343
344                formats.push(format);
345            }
346
347            Ok(formats)
348        } else {
349            let items_count = src.len() / Self::SHORT_FORMAT_SIZE;
350
351            let mut formats = Vec::with_capacity(items_count);
352
353            for _ in 0..items_count {
354                let id = src.read_u32();
355                let name_buffer = src.read_slice(32);
356
357                let mut name_cursor: ReadCursor<'_> = ReadCursor::new(name_buffer);
358                let name = read_string_from_cursor(&mut name_cursor, charset, true).map_err(|e| decode_err!(e))?;
359
360                let format = ClipboardFormat::new(ClipboardFormatId(id)).with_name(ClipboardFormatName::new(name));
361
362                formats.push(format);
363            }
364
365            Ok(formats)
366        }
367    }
368}
369
370impl<'de> Decode<'de> for FormatList<'de> {
371    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
372        let header = PartialHeader::decode(src)?;
373
374        let use_ascii = header.message_flags.contains(ClipboardPduFlags::ASCII_NAMES);
375        ensure_size!(in: src, size: header.data_length());
376
377        let encoded_formats = src.read_slice(header.data_length());
378
379        Ok(Self {
380            use_ascii,
381            encoded_formats: Cow::Borrowed(encoded_formats),
382        })
383    }
384}
385
386impl Encode for FormatList<'_> {
387    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
388        let header_flags = if self.use_ascii {
389            ClipboardPduFlags::ASCII_NAMES
390        } else {
391            ClipboardPduFlags::empty()
392        };
393
394        let header = PartialHeader::new_with_flags(cast_int!("dataLen", self.encoded_formats.len())?, header_flags);
395        header.encode(dst)?;
396
397        ensure_size!(in: dst, size: self.encoded_formats.len());
398
399        dst.write_slice(&self.encoded_formats);
400
401        Ok(())
402    }
403
404    fn name(&self) -> &'static str {
405        Self::NAME
406    }
407
408    fn size(&self) -> usize {
409        PartialHeader::SIZE + self.encoded_formats.len()
410    }
411}
412
413/// Represents `FORMAT_LIST_RESPONSE`
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
415pub enum FormatListResponse {
416    Ok,
417    Fail,
418}
419
420impl_pdu_pod!(FormatListResponse);
421
422impl FormatListResponse {
423    const NAME: &'static str = "FORMAT_LIST_RESPONSE";
424}
425
426impl Encode for FormatListResponse {
427    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
428        let header_flags = match self {
429            FormatListResponse::Ok => ClipboardPduFlags::RESPONSE_OK,
430            FormatListResponse::Fail => ClipboardPduFlags::RESPONSE_FAIL,
431        };
432
433        let header = PartialHeader::new_with_flags(0, header_flags);
434        header.encode(dst)
435    }
436
437    fn name(&self) -> &'static str {
438        Self::NAME
439    }
440
441    fn size(&self) -> usize {
442        PartialHeader::SIZE
443    }
444}
445
446impl<'de> Decode<'de> for FormatListResponse {
447    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
448        let header = PartialHeader::decode(src)?;
449        match header.message_flags {
450            ClipboardPduFlags::RESPONSE_OK => Ok(FormatListResponse::Ok),
451            ClipboardPduFlags::RESPONSE_FAIL => Ok(FormatListResponse::Fail),
452            _ => Err(invalid_field_err!("msgFlags", "Invalid format list message flags")),
453        }
454    }
455}