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(Box<str>);
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        f.write_str(&self.0)
78    }
79}
80
81/// Parses a string name into a [Name].
82pub fn parse_name(name: String) -> Result<Name, ParseNameError> {
83    validate_name(&name)?;
84    Ok(Name(name.into_boxed_str()))
85}
86
87/// Check whether a string name will be a valid input to [Name].
88pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
89    if name.len() > MAX_NAME_LENGTH {
90        return Err(ParseNameError::TooLong(name.to_string()));
91    }
92    if name.len() == 0 {
93        return Err(ParseNameError::Empty);
94    }
95    if name == "." {
96        return Err(ParseNameError::Dot);
97    }
98    if name == ".." {
99        return Err(ParseNameError::DotDot);
100    }
101    if name.chars().any(|c: char| c == '/') {
102        return Err(ParseNameError::Slash);
103    }
104    if name.chars().any(|c: char| c == '\0') {
105        return Err(ParseNameError::EmbeddedNul);
106    }
107    Ok(())
108}
109
110impl From<Name> for String {
111    fn from(value: Name) -> Self {
112        value.0.into_string()
113    }
114}
115
116impl TryFrom<String> for Name {
117    type Error = ParseNameError;
118
119    fn try_from(value: String) -> Result<Name, ParseNameError> {
120        parse_name(value)
121    }
122}
123
124impl Deref for Name {
125    type Target = str;
126
127    fn deref(&self) -> &Self::Target {
128        &self.0
129    }
130}
131
132impl Borrow<str> for Name {
133    fn borrow(&self) -> &str {
134        &*self
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use assert_matches::assert_matches;
142
143    #[test]
144    fn test_parse_name() {
145        assert_matches!(parse_name("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
146        assert_matches!(
147            parse_name(
148                std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1])
149                    .unwrap()
150                    .to_string()
151            ),
152            Err(ParseNameError::TooLong(_))
153        );
154        assert_matches!(
155            parse_name(
156                std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap().to_string()
157            ),
158            Ok(_)
159        );
160        assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
161        assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
162        assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
163        assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &*name == ".a");
164        assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &*name == "..a");
165        assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
166        assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
167        assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &*name == "abc");
168    }
169
170    #[test]
171    fn test_validate_name() {
172        assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
173        assert_matches!(
174            validate_name(
175                std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1]).unwrap()
176            ),
177            Err(ParseNameError::TooLong(_))
178        );
179        assert_matches!(
180            validate_name(std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap()),
181            Ok(())
182        );
183        assert_matches!(validate_name(""), Err(ParseNameError::Empty));
184        assert_matches!(validate_name("."), Err(ParseNameError::Dot));
185        assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
186        assert_matches!(validate_name(".a"), Ok(()));
187        assert_matches!(validate_name("..a"), Ok(()));
188        assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
189        assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
190        assert_matches!(validate_name("abc"), Ok(()));
191    }
192
193    #[test]
194    fn test_try_from() {
195        assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
196        assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &*name == "abc");
197    }
198
199    #[test]
200    fn test_into() {
201        let name = Name::try_from("a".to_string()).unwrap();
202        let name: String = name.into();
203        assert_eq!(name, "a".to_string());
204    }
205
206    #[test]
207    fn test_deref() {
208        let name = Name::try_from("a".to_string()).unwrap();
209        let name: &str = &name;
210        assert_eq!(name, "a");
211    }
212}