name/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Data structures and functions relevant to `fuchsia.io` name processing.
6//!
7//! These names may be used to designate the location of a node as it
8//! appears in a directory.
9//!
10//! These should be aligned with the library comments in sdk/fidl/fuchsia.io/io.fidl.
11
12use fidl_fuchsia_io as fio;
13use static_assertions::const_assert_eq;
14use std::borrow::Borrow;
15use std::fmt::Display;
16use std::ops::Deref;
17use thiserror::Error;
18use zx_status::Status;
19
20/// The type for the name of a node, i.e. a single path component, e.g. `foo`.
21///
22/// ## Invariants
23///
24/// A valid node name must meet the following criteria:
25///
26/// * It cannot be longer than [MAX_NAME_LENGTH].
27/// * It cannot be empty.
28/// * It cannot be ".." (dot-dot).
29/// * It cannot be "." (single dot).
30/// * It cannot contain "/".
31/// * It cannot contain embedded NUL.
32#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
33pub struct Name(String);
34
35/// The maximum length, in bytes, of a single filesystem component.
36pub const MAX_NAME_LENGTH: usize = fio::MAX_NAME_LENGTH as usize;
37const_assert_eq!(MAX_NAME_LENGTH as u64, fio::MAX_NAME_LENGTH);
38
39#[derive(Error, Debug, Clone)]
40pub enum ParseNameError {
41    #[error("name `{0}` is too long")]
42    TooLong(String),
43
44    #[error("name cannot be empty")]
45    Empty,
46
47    #[error("name cannot be `.`")]
48    Dot,
49
50    #[error("name cannot be `..`")]
51    DotDot,
52
53    #[error("name cannot contain `/`")]
54    Slash,
55
56    #[error("name cannot contain embedded NUL")]
57    EmbeddedNul,
58}
59
60impl From<ParseNameError> for Status {
61    fn from(value: ParseNameError) -> Self {
62        match value {
63            ParseNameError::TooLong(_) => Status::BAD_PATH,
64            _ => Status::INVALID_ARGS,
65        }
66    }
67}
68
69impl Name {
70    pub fn from<S: Into<String>>(s: S) -> Result<Name, ParseNameError> {
71        parse_name(s.into())
72    }
73}
74
75impl Display for Name {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        let value = &self.0;
78        write!(f, "{value}")
79    }
80}
81
82/// Parses a string name into a [Name].
83pub fn parse_name(name: String) -> Result<Name, ParseNameError> {
84    validate_name(&name)?;
85    Ok(Name(name))
86}
87
88/// Check whether a string name will be a valid input to [Name].
89pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
90    if name.len() > MAX_NAME_LENGTH {
91        return Err(ParseNameError::TooLong(name.to_string()));
92    }
93    if name.len() == 0 {
94        return Err(ParseNameError::Empty);
95    }
96    if name == "." {
97        return Err(ParseNameError::Dot);
98    }
99    if name == ".." {
100        return Err(ParseNameError::DotDot);
101    }
102    if name.chars().any(|c: char| c == '/') {
103        return Err(ParseNameError::Slash);
104    }
105    if name.chars().any(|c: char| c == '\0') {
106        return Err(ParseNameError::EmbeddedNul);
107    }
108    Ok(())
109}
110
111impl From<Name> for String {
112    fn from(value: Name) -> Self {
113        value.0
114    }
115}
116
117impl TryFrom<String> for Name {
118    type Error = ParseNameError;
119
120    fn try_from(value: String) -> Result<Name, ParseNameError> {
121        parse_name(value)
122    }
123}
124
125impl Deref for Name {
126    type Target = str;
127
128    fn deref(&self) -> &Self::Target {
129        &self.0
130    }
131}
132
133impl Borrow<str> for Name {
134    fn borrow(&self) -> &str {
135        &*self
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use assert_matches::assert_matches;
143
144    #[test]
145    fn test_parse_name() {
146        assert_matches!(parse_name("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
147        assert_matches!(
148            parse_name(
149                std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap().to_string()
150            ),
151            Err(ParseNameError::TooLong(_))
152        );
153        assert_matches!(
154            parse_name(
155                std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap().to_string()
156            ),
157            Ok(_)
158        );
159        assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
160        assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
161        assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
162        assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &name == ".a");
163        assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &name == "..a");
164        assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
165        assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
166        assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &name == "abc");
167    }
168
169    #[test]
170    fn test_validate_name() {
171        assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
172        assert_matches!(
173            validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap()),
174            Err(ParseNameError::TooLong(_))
175        );
176        assert_matches!(
177            validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap()),
178            Ok(())
179        );
180        assert_matches!(validate_name(""), Err(ParseNameError::Empty));
181        assert_matches!(validate_name("."), Err(ParseNameError::Dot));
182        assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
183        assert_matches!(validate_name(".a"), Ok(()));
184        assert_matches!(validate_name("..a"), Ok(()));
185        assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
186        assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
187        assert_matches!(validate_name("abc"), Ok(()));
188    }
189
190    #[test]
191    fn test_try_from() {
192        assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
193        assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &name == "abc");
194    }
195
196    #[test]
197    fn test_into() {
198        let name = Name::try_from("a".to_string()).unwrap();
199        let name: String = name.into();
200        assert_eq!(name, "a".to_string());
201    }
202
203    #[test]
204    fn test_deref() {
205        let name = Name::try_from("a".to_string()).unwrap();
206        let name: &str = &name;
207        assert_eq!(name, "a");
208    }
209}