iceoryx2_bb_system_types/
file_name.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! Relocatable (inter-process shared memory compatible) [`semantic_string::SemanticString`] implementation for
14//! [`FileName`]. All modification operations ensure that never an
15//! invalid file or path name can be generated. All strings have a fixed size so that the maximum
16//! path or file name length the system supports can be stored.
17//!
18//! # Example
19//!
20//! ```
21//! use iceoryx2_bb_container::semantic_string::SemanticString;
22//! use iceoryx2_bb_system_types::file_name::*;
23//!
24//! let name = FileName::new(b"some_file.txt");
25//!
26//! let invalid_name = FileName::new(b"no/path/allowed.txt");
27//! assert!(invalid_name.is_err());
28//! ```
29
30pub use iceoryx2_bb_container::semantic_string::SemanticString;
31use iceoryx2_bb_elementary::static_assert::{static_assert_ge, static_assert_le};
32
33use core::hash::{Hash, Hasher};
34use iceoryx2_bb_container::semantic_string;
35use iceoryx2_pal_configuration::FILENAME_LENGTH;
36
37fn invalid_characters(value: &[u8]) -> bool {
38    for c in value {
39        match c {
40            // linux & windows
41            0 => return true,
42            b'/' => return true,
43            // windows only
44            1..=31 => return true,
45            b':' => return true,
46            b'\\' => return true,
47            b'<' => return true,
48            b'>' => return true,
49            b'"' => return true,
50            b'|' => return true,
51            b'?' => return true,
52            b'*' => return true,
53            _ => (),
54        }
55    }
56    false
57}
58
59fn invalid_content(value: &[u8]) -> bool {
60    matches!(value, b"" | b"." | b"..")
61}
62
63fn normalize(this: &FileName) -> FileName {
64    *this
65}
66
67semantic_string! {
68  /// Represents a file name. The restriction are choosen in a way that it is platform independent.
69  /// This means characters/strings which would be legal on some platforms are forbidden as well.
70  name: FileName,
71  capacity: FILENAME_LENGTH,
72  invalid_content: invalid_content,
73  invalid_characters: invalid_characters,
74  normalize: normalize
75}
76
77#[derive(Debug, Clone, Copy, Eq)]
78pub struct RestrictedFileName<const CAPACITY: usize> {
79    value: iceoryx2_bb_container::byte_string::FixedSizeByteString<CAPACITY>,
80}
81
82impl<const CAPACITY: usize>
83    iceoryx2_bb_container::semantic_string::internal::SemanticStringAccessor<CAPACITY>
84    for RestrictedFileName<CAPACITY>
85{
86    unsafe fn new_empty() -> Self {
87        static_assert_le::<{ CAPACITY }, { FILENAME_LENGTH }>();
88        static_assert_ge::<{ CAPACITY }, 1>();
89
90        Self {
91            value: iceoryx2_bb_container::byte_string::FixedSizeByteString::new(),
92        }
93    }
94
95    unsafe fn get_mut_string(
96        &mut self,
97    ) -> &mut iceoryx2_bb_container::byte_string::FixedSizeByteString<CAPACITY> {
98        &mut self.value
99    }
100
101    fn is_invalid_content(string: &[u8]) -> bool {
102        if Self::does_contain_invalid_characters(string) {
103            return true;
104        }
105
106        invalid_content(string)
107    }
108
109    fn does_contain_invalid_characters(string: &[u8]) -> bool {
110        if core::str::from_utf8(string).is_err() {
111            return true;
112        }
113
114        invalid_characters(string)
115    }
116}
117
118// BEGIN: serde
119pub(crate) mod visitor_type {
120    pub(crate) struct RestrictedFileName<const CAPACITY: usize>;
121}
122
123impl<const CAPACITY: usize> serde::de::Visitor<'_> for visitor_type::RestrictedFileName<CAPACITY> {
124    type Value = RestrictedFileName<CAPACITY>;
125
126    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
127        formatter.write_str("a string containing the service name")
128    }
129
130    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
131    where
132        E: serde::de::Error,
133    {
134        RestrictedFileName::<CAPACITY>::new(v.as_bytes()).map_err(|e| {
135            E::custom(std::format!(
136                "invalid RestrictedFileName<{}> provided {:?} ({:?}).",
137                CAPACITY,
138                v,
139                e
140            ))
141        })
142    }
143}
144
145impl<'de, const CAPACITY: usize> serde::Deserialize<'de> for RestrictedFileName<CAPACITY> {
146    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147    where
148        D: serde::Deserializer<'de>,
149    {
150        deserializer.deserialize_str(visitor_type::RestrictedFileName::<CAPACITY>)
151    }
152}
153
154impl<const CAPACITY: usize> serde::Serialize for RestrictedFileName<CAPACITY> {
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: serde::Serializer,
158    {
159        serializer.serialize_str(std::str::from_utf8(self.as_bytes()).unwrap())
160    }
161}
162// END: serde
163
164impl<const CAPACITY: usize> iceoryx2_bb_container::semantic_string::SemanticString<CAPACITY>
165    for RestrictedFileName<CAPACITY>
166{
167    fn as_string(&self) -> &iceoryx2_bb_container::byte_string::FixedSizeByteString<CAPACITY> {
168        &self.value
169    }
170
171    fn normalize(&self) -> Self {
172        *self
173    }
174
175    unsafe fn new_unchecked(bytes: &[u8]) -> Self {
176        Self {
177            value: iceoryx2_bb_container::byte_string::FixedSizeByteString::new_unchecked(bytes),
178        }
179    }
180
181    unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
182        self.value.insert_bytes_unchecked(idx, bytes);
183    }
184}
185
186impl<const CAPACITY: usize> RestrictedFileName<CAPACITY> {
187    /// Returns the maximum length of [`$string`]
188    pub const fn max_len() -> usize {
189        CAPACITY
190    }
191}
192
193impl<const CAPACITY: usize> std::fmt::Display for RestrictedFileName<CAPACITY> {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        std::write!(f, "{}", self.value)
196    }
197}
198
199impl<const CAPACITY: usize> Hash for RestrictedFileName<CAPACITY> {
200    fn hash<H: Hasher>(&self, state: &mut H) {
201        self.normalize().as_bytes().hash(state)
202    }
203}
204
205impl<const CAPACITY: usize> From<RestrictedFileName<CAPACITY>> for FileName {
206    fn from(value: RestrictedFileName<CAPACITY>) -> FileName {
207        // SAFETY: It is ensured that the RestrictedFileName contains always a valid FileName, just
208        // with less capacity
209        unsafe { FileName::new_unchecked(value.as_bytes()) }
210    }
211}
212
213impl<const CAPACITY: usize> From<RestrictedFileName<CAPACITY>> for String {
214    fn from(value: RestrictedFileName<CAPACITY>) -> String {
215        // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
216        unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
217    }
218}
219
220impl<const CAPACITY: usize> From<&RestrictedFileName<CAPACITY>> for String {
221    fn from(value: &RestrictedFileName<CAPACITY>) -> String {
222        // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
223        unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
224    }
225}
226
227impl<const CAPACITY: usize> std::convert::TryFrom<&str> for RestrictedFileName<CAPACITY> {
228    type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
229
230    fn try_from(value: &str) -> Result<Self, Self::Error> {
231        Self::new(value.as_bytes())
232    }
233}
234
235impl<const CAPACITY: usize> std::convert::TryFrom<&FileName> for RestrictedFileName<CAPACITY> {
236    type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
237
238    fn try_from(value: &FileName) -> Result<Self, Self::Error> {
239        Self::new(value.as_bytes())
240    }
241}
242
243impl<const CAPACITY: usize> PartialEq<RestrictedFileName<CAPACITY>>
244    for RestrictedFileName<CAPACITY>
245{
246    fn eq(&self, other: &RestrictedFileName<CAPACITY>) -> bool {
247        *self.normalize().as_bytes() == *other.normalize().as_bytes()
248    }
249}
250
251impl<const CAPACITY: usize> PartialEq<&[u8]> for RestrictedFileName<CAPACITY> {
252    fn eq(&self, other: &&[u8]) -> bool {
253        let other = match RestrictedFileName::<CAPACITY>::new(other) {
254            Ok(other) => other,
255            Err(_) => return false,
256        };
257
258        *self == other
259    }
260}
261
262impl<const CAPACITY: usize> PartialEq<&[u8]> for &RestrictedFileName<CAPACITY> {
263    fn eq(&self, other: &&[u8]) -> bool {
264        let other = match RestrictedFileName::<CAPACITY>::new(other) {
265            Ok(other) => other,
266            Err(_) => return false,
267        };
268
269        **self == other
270    }
271}
272
273impl<const CAPACITY: usize> PartialEq<[u8; CAPACITY]> for RestrictedFileName<CAPACITY> {
274    fn eq(&self, other: &[u8; CAPACITY]) -> bool {
275        let other = match RestrictedFileName::<CAPACITY>::new(other) {
276            Ok(other) => other,
277            Err(_) => return false,
278        };
279
280        *self == other
281    }
282}
283
284impl<const CAPACITY: usize> PartialEq<&[u8; CAPACITY]> for RestrictedFileName<CAPACITY> {
285    fn eq(&self, other: &&[u8; CAPACITY]) -> bool {
286        // TODO: false positive from clippy
287        #[allow(clippy::explicit_auto_deref)]
288        let other = match RestrictedFileName::<CAPACITY>::new(*other) {
289            Ok(other) => other,
290            Err(_) => return false,
291        };
292
293        *self == other
294    }
295}
296
297impl<const CAPACITY: usize> std::ops::Deref for RestrictedFileName<CAPACITY> {
298    type Target = [u8];
299
300    fn deref(&self) -> &Self::Target {
301        self.value.as_bytes()
302    }
303}