abstract_std/objects/validation/
verifiers.rs

1use super::ValidationError;
2
3pub(crate) const MIN_DESC_LENGTH: usize = 1;
4pub(crate) const MAX_DESC_LENGTH: usize = 1024;
5/// Minimum link length is 11, because the shortest url could be http://a.be
6pub(crate) const MIN_LINK_LENGTH: usize = 11;
7pub(crate) const MAX_LINK_LENGTH: usize = 128;
8pub(crate) const MIN_TITLE_LENGTH: usize = 1;
9pub(crate) const MAX_TITLE_LENGTH: usize = 64;
10
11pub(crate) const DANGEROUS_CHARS: &[char] = &['"', '\'', '=', '>', '<'];
12
13fn contains_dangerous_characters(input: &str) -> bool {
14    input.chars().any(|c| DANGEROUS_CHARS.contains(&c))
15}
16
17fn is_valid_url(link: &str) -> bool {
18    link.starts_with("http://") || link.starts_with("https://") || link.starts_with("ipfs://")
19}
20
21pub fn validate_link(link: Option<&str>) -> Result<(), ValidationError> {
22    if let Some(link) = link {
23        if link.len() < MIN_LINK_LENGTH {
24            Err(ValidationError::LinkInvalidShort(MIN_LINK_LENGTH))
25        } else if link.len() > MAX_LINK_LENGTH {
26            Err(ValidationError::LinkInvalidLong(MAX_LINK_LENGTH))
27        } else if !is_valid_url(link) {
28            Err(ValidationError::LinkInvalidFormat {})
29        } else if contains_dangerous_characters(link) {
30            Err(ValidationError::LinkContainsDangerousCharacters {})
31        } else {
32            Ok(())
33        }
34    } else {
35        Ok(())
36    }
37}
38
39pub fn validate_name(title: &str) -> Result<(), ValidationError> {
40    if title.len() < MIN_TITLE_LENGTH {
41        Err(ValidationError::TitleInvalidShort(MIN_TITLE_LENGTH))
42    } else if title.len() > MAX_TITLE_LENGTH {
43        Err(ValidationError::TitleInvalidLong(MAX_TITLE_LENGTH))
44    } else if contains_dangerous_characters(title) {
45        Err(ValidationError::TitleContainsDangerousCharacters {})
46    } else {
47        Ok(())
48    }
49}
50
51pub fn validate_description(maybe_description: Option<&str>) -> Result<(), ValidationError> {
52    if let Some(description) = maybe_description {
53        if description.len() < MIN_DESC_LENGTH {
54            return Err(ValidationError::DescriptionInvalidShort(MIN_DESC_LENGTH));
55        } else if description.len() > MAX_DESC_LENGTH {
56            return Err(ValidationError::DescriptionInvalidLong(MAX_DESC_LENGTH));
57        } else if contains_dangerous_characters(description) {
58            return Err(ValidationError::DescriptionContainsDangerousCharacters {});
59        }
60    }
61    Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66    use rstest::rstest;
67
68    use super::*;
69
70    mod link {
71        use super::*;
72
73        #[rstest(
74            input,
75            case("https://www.google.com"),
76            case("http://example.com"),
77            case("https://example.net:8080")
78        )]
79        fn valid(input: &str) {
80            assert!(validate_link(Some(input)).is_ok());
81        }
82
83        #[rstest(
84            input,
85            case("http://a.b"),
86            case("://example.com"),
87            case("example.com"),
88            case("https://example.org/path?query=value"),
89            case("https:/example.com")
90        )]
91        fn invalid(input: &str) {
92            assert!(validate_link(Some(input)).is_err());
93        }
94    }
95
96    mod name {
97        use super::*;
98
99        #[rstest(input,
100        case("name"),
101        case("name123"),
102        case("name 123"),
103        case("a"),
104        case(& "a".repeat(MAX_TITLE_LENGTH)),
105        case("name!$%&*+,-.;@^_`|~"),
106        case("名前"),
107        )]
108        fn valid_names(input: &str) {
109            assert!(validate_name(input).is_ok());
110        }
111
112        #[rstest(input,
113        case(""),
114        case(& "a".repeat(MAX_TITLE_LENGTH + 1)),
115        case("name<>'\""),
116        )]
117        fn invalid_names(input: &str) {
118            assert!(validate_name(input).is_err());
119        }
120    }
121
122    mod description {
123        use super::*;
124
125        #[rstest(input,
126        case("d"),
127        case("description123"),
128        case("description 123"),
129        case(& "a".repeat(MAX_DESC_LENGTH)),
130        case("description!$%&*+,-.;@^_`|~"),
131        case("説明"),
132        )]
133        fn valid_descriptions(input: &str) {
134            assert!(validate_description(Some(input)).is_ok());
135        }
136
137        #[rstest(input,
138        case(""),
139        case(& "a".repeat(MAX_DESC_LENGTH + 1)),
140        case("description<>'\""),
141        )]
142        fn invalid_descriptions(input: &str) {
143            assert!(validate_description(Some(input)).is_err());
144        }
145    }
146}