fuchsia_archive/
name.rs

1// Copyright 2020 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
5use crate::Error;
6
7enum CurrentSegment {
8    Empty,
9    Dot,
10    DotDot,
11    Other,
12}
13use CurrentSegment::*;
14
15/// Does not validate max name length of 2**16 - 1, as this is not needed on the read path and can
16/// be done separately conveniently on the write path.
17pub fn validate_name(name: &[u8]) -> Result<&[u8], Error> {
18    let mut state = Empty;
19    for c in name.iter() {
20        state = match c {
21            b'\0' => return Err(Error::NameContainsNull(name.into())),
22            b'/' => match state {
23                Empty => {
24                    if name[0] == b'/' {
25                        return Err(Error::NameStartsWithSlash(name.into()));
26                    }
27                    return Err(Error::NameContainsEmptySegment(name.into()));
28                }
29                Dot => return Err(Error::NameContainsDotSegment(name.into())),
30                DotDot => return Err(Error::NameContainsDotDotSegment(name.into())),
31                Other => Empty,
32            },
33            b'.' => match state {
34                Empty => Dot,
35                Dot => DotDot,
36                DotDot | Other => Other,
37            },
38            _ => Other,
39        }
40    }
41
42    match state {
43        Empty => {
44            if name.is_empty() {
45                Err(Error::ZeroLengthName)
46            } else {
47                Err(Error::NameEndsWithSlash(name.into()))
48            }
49        }
50        Dot => Err(Error::NameContainsDotSegment(name.into())),
51        DotDot => Err(Error::NameContainsDotDotSegment(name.into())),
52        Other => Ok(name),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use assert_matches::assert_matches;
60
61    #[test]
62    fn valid_names() {
63        for name in
64            ["a", "a/a", "a/a/a", ".a", "a.", "..a", "a..", "a./a", "a../a", "a/.a", "a/..a"].iter()
65        {
66            assert_matches!(validate_name(name.as_bytes()), Ok(n) if n == name.as_bytes());
67        }
68    }
69
70    #[test]
71    fn invalid_names() {
72        assert_matches!(validate_name(b""), Err(Error::ZeroLengthName));
73
74        for name in ["/", "/a"].iter() {
75            assert_matches!(
76                validate_name(name.as_bytes()),
77                Err(Error::NameStartsWithSlash(n)) if n == name.as_bytes()
78            );
79        }
80
81        for name in ["a/", "aa/"].iter() {
82            assert_matches!(
83                validate_name(name.as_bytes()),
84                Err(Error::NameEndsWithSlash(n)) if n == name.as_bytes()
85            );
86        }
87
88        for name in ["\0", "a\0", "\0a", "a/\0", "\0/a"].iter() {
89            assert_matches!(
90                validate_name(name.as_bytes()),
91                Err(Error::NameContainsNull(n)) if n == name.as_bytes()
92            );
93        }
94
95        for name in ["a//a", "a/a//a"].iter() {
96            assert_matches!(
97                validate_name(name.as_bytes()),
98                Err(Error::NameContainsEmptySegment(n)) if n == name.as_bytes()
99            );
100        }
101
102        for name in [".", "./a", "a/.", "a/./a"].iter() {
103            assert_matches!(
104                validate_name(name.as_bytes()),
105                Err(Error::NameContainsDotSegment(n)) if n == name.as_bytes()
106            );
107        }
108
109        for name in ["..", "../a", "a/..", "a/../a"].iter() {
110            assert_matches!(
111                validate_name(name.as_bytes()),
112                Err(Error::NameContainsDotDotSegment(n)) if n == name.as_bytes()
113            );
114        }
115    }
116}