fuchsia_archive/
name.rs

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::Error;

enum CurrentSegment {
    Empty,
    Dot,
    DotDot,
    Other,
}
use CurrentSegment::*;

/// Does not validate max name length of 2**16 - 1, as this is not needed on the read path and can
/// be done separately conveniently on the write path.
pub fn validate_name(name: &[u8]) -> Result<&[u8], Error> {
    let mut state = Empty;
    for c in name.iter() {
        state = match c {
            b'\0' => return Err(Error::NameContainsNull(name.into())),
            b'/' => match state {
                Empty => {
                    if name[0] == b'/' {
                        return Err(Error::NameStartsWithSlash(name.into()));
                    }
                    return Err(Error::NameContainsEmptySegment(name.into()));
                }
                Dot => return Err(Error::NameContainsDotSegment(name.into())),
                DotDot => return Err(Error::NameContainsDotDotSegment(name.into())),
                Other => Empty,
            },
            b'.' => match state {
                Empty => Dot,
                Dot => DotDot,
                DotDot | Other => Other,
            },
            _ => Other,
        }
    }

    match state {
        Empty => {
            if name.is_empty() {
                Err(Error::ZeroLengthName)
            } else {
                Err(Error::NameEndsWithSlash(name.into()))
            }
        }
        Dot => Err(Error::NameContainsDotSegment(name.into())),
        DotDot => Err(Error::NameContainsDotDotSegment(name.into())),
        Other => Ok(name),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert_matches::assert_matches;

    #[test]
    fn valid_names() {
        for name in
            ["a", "a/a", "a/a/a", ".a", "a.", "..a", "a..", "a./a", "a../a", "a/.a", "a/..a"].iter()
        {
            assert_matches!(validate_name(name.as_bytes()), Ok(n) if n == name.as_bytes());
        }
    }

    #[test]
    fn invalid_names() {
        assert_matches!(validate_name(b""), Err(Error::ZeroLengthName));

        for name in ["/", "/a"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameStartsWithSlash(n)) if n == name.as_bytes()
            );
        }

        for name in ["a/", "aa/"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameEndsWithSlash(n)) if n == name.as_bytes()
            );
        }

        for name in ["\0", "a\0", "\0a", "a/\0", "\0/a"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameContainsNull(n)) if n == name.as_bytes()
            );
        }

        for name in ["a//a", "a/a//a"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameContainsEmptySegment(n)) if n == name.as_bytes()
            );
        }

        for name in [".", "./a", "a/.", "a/./a"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameContainsDotSegment(n)) if n == name.as_bytes()
            );
        }

        for name in ["..", "../a", "a/..", "a/../a"].iter() {
            assert_matches!(
                validate_name(name.as_bytes()),
                Err(Error::NameContainsDotDotSegment(n)) if n == name.as_bytes()
            );
        }
    }
}