about summary refs log tree commit diff
path: root/tvix/castore/src/path/component.rs
blob: f755f06e62a814ba8eea5e477a286302b04e716f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// TODO: split out this error
use crate::DirectoryError;

use bstr::ByteSlice;
use std::fmt::{self, Debug, Display};

/// A wrapper type for validated path components in the castore model.
/// Internally uses a [bytes::Bytes], but disallows
/// slashes, and null bytes to be present, as well as
/// '.', '..' and the empty string.
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PathComponent {
    pub(super) inner: bytes::Bytes,
}

impl AsRef<[u8]> for PathComponent {
    fn as_ref(&self) -> &[u8] {
        self.inner.as_ref()
    }
}

impl From<PathComponent> for bytes::Bytes {
    fn from(value: PathComponent) -> Self {
        value.inner
    }
}

pub(super) fn is_valid_name<B: AsRef<[u8]>>(name: B) -> bool {
    let v = name.as_ref();

    !v.is_empty() && v != *b".." && v != *b"." && !v.contains(&0x00) && !v.contains(&b'/')
}

impl TryFrom<bytes::Bytes> for PathComponent {
    type Error = DirectoryError;

    fn try_from(value: bytes::Bytes) -> Result<Self, Self::Error> {
        if !is_valid_name(&value) {
            return Err(DirectoryError::InvalidName(value));
        }

        Ok(Self { inner: value })
    }
}

impl TryFrom<&'static [u8]> for PathComponent {
    type Error = DirectoryError;

    fn try_from(value: &'static [u8]) -> Result<Self, Self::Error> {
        if !is_valid_name(value) {
            return Err(DirectoryError::InvalidName(bytes::Bytes::from_static(
                value,
            )));
        }
        Ok(Self {
            inner: bytes::Bytes::from_static(value),
        })
    }
}

impl TryFrom<&str> for PathComponent {
    type Error = DirectoryError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        if !is_valid_name(value) {
            return Err(DirectoryError::InvalidName(bytes::Bytes::copy_from_slice(
                value.as_bytes(),
            )));
        }
        Ok(Self {
            inner: bytes::Bytes::copy_from_slice(value.as_bytes()),
        })
    }
}

impl TryFrom<&std::ffi::CStr> for PathComponent {
    type Error = DirectoryError;

    fn try_from(value: &std::ffi::CStr) -> Result<Self, Self::Error> {
        if !is_valid_name(value.to_bytes()) {
            return Err(DirectoryError::InvalidName(bytes::Bytes::copy_from_slice(
                value.to_bytes(),
            )));
        }
        Ok(Self {
            inner: bytes::Bytes::copy_from_slice(value.to_bytes()),
        })
    }
}

impl Debug for PathComponent {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self.inner.as_bstr(), f)
    }
}

impl Display for PathComponent {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self.inner.as_bstr(), f)
    }
}