smithay_clipboard/
mime.rs

1use std::borrow::Cow;
2use std::{error, fmt};
3
4/// List of allowed mimes.
5pub static ALLOWED_TEXT_MIME_TYPES: [&str; 3] =
6    ["text/plain;charset=utf-8", "UTF8_STRING", "text/plain"];
7
8#[derive(Debug, Clone, Copy)]
9pub struct Error;
10
11impl fmt::Display for Error {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        write!(f, "Unsupported mime type")
14    }
15}
16
17impl error::Error for Error {}
18
19#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
20pub enum Text {
21    #[default]
22    /// text/plain;charset=utf-8 mime type.
23    ///
24    /// The primary mime type used by most clients
25    TextPlainUtf8 = 0,
26    /// UTF8_STRING mime type.
27    ///
28    /// Some X11 clients are using only this mime type, so we
29    /// should have it as a fallback just in case.
30    Utf8String = 1,
31    /// text/plain mime type.
32    ///
33    /// Fallback without charset parameter.
34    TextPlain = 2,
35}
36
37/// Mime type supported by clipboard.
38#[derive(Clone, Eq, PartialEq, Debug)]
39pub enum MimeType {
40    /// Text mime type.
41    Text(Text),
42    /// Other mime type.
43    Other(Cow<'static, str>),
44}
45
46impl Default for MimeType {
47    fn default() -> Self {
48        MimeType::Text(Text::default())
49    }
50}
51
52impl From<Cow<'static, str>> for MimeType {
53    fn from(value: Cow<'static, str>) -> Self {
54        if let Some(pos) = ALLOWED_TEXT_MIME_TYPES.into_iter().position(|allowed| allowed == value)
55        {
56            MimeType::Text(match pos {
57                0 => Text::TextPlainUtf8,
58                1 => Text::Utf8String,
59                2 => Text::TextPlain,
60                _ => unreachable!(),
61            })
62        } else {
63            MimeType::Other(value)
64        }
65    }
66}
67
68impl AsRef<str> for MimeType {
69    fn as_ref(&self) -> &str {
70        match self {
71            MimeType::Other(s) => s.as_ref(),
72            MimeType::Text(text) => ALLOWED_TEXT_MIME_TYPES[*text as usize],
73        }
74    }
75}
76
77/// Describes the mime types which are accepted.
78pub trait AllowedMimeTypes: TryFrom<(Vec<u8>, MimeType)> {
79    /// List allowed mime types for the type to convert from a byte slice.
80    ///
81    /// Allowed mime types should be listed in order of decreasing preference,
82    /// most preferred first.
83    fn allowed() -> Cow<'static, [MimeType]>;
84}
85
86/// Can be converted to data with the available mime types.
87pub trait AsMimeTypes {
88    /// List available mime types for this data to convert to a byte slice.
89    fn available(&self) -> Cow<'static, [MimeType]>;
90
91    /// Converts a type to a byte slice for the given mime type if possible.
92    fn as_bytes(&self, mime_type: &MimeType) -> Option<Cow<'static, [u8]>>;
93}
94
95impl MimeType {
96    /// Find first offered mime type among the `allowed_mime_types`.
97    ///
98    /// `find_allowed()` searches for mime type clipboard supports, if we have a
99    /// match, returns `Some(MimeType)`, otherwise `None`.
100    pub(crate) fn find_allowed(
101        offered_mime_types: &[String],
102        allowed_mime_types: &[Self],
103    ) -> Option<Self> {
104        allowed_mime_types
105            .iter()
106            .find(|allowed| {
107                offered_mime_types.iter().any(|offered| offered.as_str() == allowed.as_ref())
108            })
109            .cloned()
110    }
111}
112
113impl std::fmt::Display for MimeType {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            MimeType::Other(m) => write!(f, "{}", m),
117            MimeType::Text(text) => write!(f, "{}", ALLOWED_TEXT_MIME_TYPES[*text as usize]),
118        }
119    }
120}
121
122/// Normalize CR and CRLF into LF.
123///
124/// 'text' mime types require CRLF line ending according to
125/// RFC-2046, however the platform line terminator and what applications
126/// expect is LF.
127pub fn normalize_to_lf(text: String) -> String {
128    text.replace("\r\n", "\n").replace('\r', "\n")
129}
130
131#[cfg(test)]
132mod tests {
133    use std::borrow::Cow;
134
135    use crate::mime::{MimeType, ALLOWED_TEXT_MIME_TYPES};
136
137    #[test]
138    fn test_from_str() {
139        assert_eq!(
140            MimeType::from(Cow::Borrowed(ALLOWED_TEXT_MIME_TYPES[0])),
141            MimeType::Text(crate::mime::Text::TextPlainUtf8)
142        );
143        assert_eq!(
144            MimeType::from(Cow::Borrowed(ALLOWED_TEXT_MIME_TYPES[1])),
145            MimeType::Text(crate::mime::Text::Utf8String)
146        );
147        assert_eq!(
148            MimeType::from(Cow::Borrowed(ALLOWED_TEXT_MIME_TYPES[2])),
149            MimeType::Text(crate::mime::Text::TextPlain)
150        );
151    }
152}