http_types/
parse_utils.rs
1use std::borrow::Cow;
2
3pub(crate) fn parse_token(input: &str) -> (Option<&str>, &str) {
5 let mut end_of_token = 0;
6 for (i, c) in input.char_indices() {
7 if tchar(c) {
8 end_of_token = i + 1;
9 } else {
10 break;
11 }
12 }
13
14 if end_of_token == 0 {
15 (None, input)
16 } else {
17 (Some(&input[..end_of_token]), &input[end_of_token..])
18 }
19}
20
21fn tchar(c: char) -> bool {
23 matches!(
24 c, 'a'..='z'
25 | 'A'..='Z'
26 | '0'..='9'
27 | '!'
28 | '#'
29 | '$'
30 | '%'
31 | '&'
32 | '\''
33 | '*'
34 | '+'
35 | '-'
36 | '.'
37 | '^'
38 | '_'
39 | '`'
40 | '|'
41 | '~'
42 )
43}
44
45fn vchar(c: char) -> bool {
47 matches!(c as u8, b'\t' | 32..=126 | 128..=255)
48}
49
50pub(crate) fn parse_quoted_string(input: &str) -> (Option<Cow<'_, str>>, &str) {
52 if !input.starts_with('"') {
54 return (None, input);
55 }
56
57 let mut end_of_string = None;
58 let mut backslashes: Vec<usize> = vec![];
59
60 for (i, c) in input.char_indices().skip(1) {
61 if i > 1 && backslashes.last() == Some(&(i - 2)) {
62 if !vchar(c) {
63 return (None, input);
65 }
66 } else {
68 match c as u8 {
69 b'\\' => {
71 backslashes.push(i - 1);
72 }
73
74 b'"' => {
76 end_of_string = Some(i + 1);
77 break;
78 }
79
80 b'\t' | b' ' | 15 | 35..=91 | 93..=126 | 128..=255 => {}
82
83 _ => return (None, input),
85 }
86 }
87 }
88
89 if let Some(end_of_string) = end_of_string {
90 let value = &input[1..end_of_string - 1]; let value = if backslashes.is_empty() {
93 value.into()
95 } else {
96 backslashes.reverse(); value
99 .char_indices()
100 .filter_map(|(i, c)| {
101 if Some(&i) == backslashes.last() {
102 backslashes.pop();
104 None } else {
106 Some(c)
107 }
108 })
109 .collect::<String>()
110 .into()
111 };
112
113 (Some(value), &input[end_of_string..])
114 } else {
115 (None, input)
117 }
118}
119
120#[cfg(test)]
121mod test {
122 use super::*;
123 #[test]
124 fn token_successful_parses() {
125 assert_eq!(parse_token("key=value"), (Some("key"), "=value"));
126 assert_eq!(parse_token("KEY=value"), (Some("KEY"), "=value"));
127 assert_eq!(parse_token("0123)=value"), (Some("0123"), ")=value"));
128 assert_eq!(parse_token("a=b"), (Some("a"), "=b"));
129 assert_eq!(
130 parse_token("!#$%&'*+-.^_`|~=value"),
131 (Some("!#$%&'*+-.^_`|~"), "=value",)
132 );
133 }
134
135 #[test]
136 fn token_unsuccessful_parses() {
137 assert_eq!(parse_token(""), (None, ""));
138 assert_eq!(parse_token("=value"), (None, "=value"));
139 for c in r#"(),/:;<=>?@[\]{}"#.chars() {
140 let s = c.to_string();
141 assert_eq!(parse_token(&s), (None, &*s));
142
143 let s = format!("match{}rest", s);
144 assert_eq!(parse_token(&s), (Some("match"), &*format!("{}rest", c)));
145 }
146 }
147
148 #[test]
149 fn qstring_successful_parses() {
150 assert_eq!(
151 parse_quoted_string(r#""key"=value"#),
152 (Some(Cow::Borrowed("key")), "=value")
153 );
154
155 assert_eq!(
156 parse_quoted_string(r#""escaped \" quote \""rest"#),
157 (
158 Some(Cow::Owned(String::from(r#"escaped " quote ""#))),
159 r#"rest"#
160 )
161 );
162 }
163
164 #[test]
165 fn qstring_unsuccessful_parses() {
166 assert_eq!(parse_quoted_string(r#""abc"#), (None, "\"abc"));
167 assert_eq!(parse_quoted_string(r#"hello""#), (None, "hello\"",));
168 assert_eq!(parse_quoted_string(r#"=value\"#), (None, "=value\\"));
169 assert_eq!(parse_quoted_string(r#"\""#), (None, r#"\""#));
170 assert_eq!(parse_quoted_string(r#""\""#), (None, r#""\""#));
171 }
172}