1use structured_asn1::memory::{MemoryError, Work};
4
5mod headers;
6pub use headers::media_type_or_headers;
7
8#[derive(Clone, Copy)]
9enum Phase {
10 TypeStart,
11 Type,
12 Slash,
13 SubtypeStart,
14 Subtype,
15 Parameters,
16 AttributeStart,
17 Attribute,
18 Equals,
19 ValueStart,
20 Value,
21 AfterValue,
22}
23
24#[derive(Clone, Copy)]
25enum Context {
26 Normal,
27 Comment { depth: usize, escaped: bool },
28 Quoted { escaped: bool },
29}
30
31pub struct Syntax<const XML_NORMALIZED: bool = false> {
49 valid: bool,
50 phase: Phase,
51 context: Context,
52 folding: u8,
53}
54
55impl<const XML_NORMALIZED: bool> Syntax<XML_NORMALIZED> {
56 #[must_use]
58 pub const fn new() -> Self {
59 Self {
60 valid: true,
61 phase: Phase::TypeStart,
62 context: Context::Normal,
63 folding: 0,
64 }
65 }
66
67 fn byte(&mut self, byte: u8) -> bool {
73 if !byte.is_ascii() || (byte == b'\n' && self.folding != 1 && !XML_NORMALIZED) {
74 return false;
75 }
76 if self.folding != 0 {
77 if self.folding == 1 && byte == b'\n' {
78 self.folding = 2;
79 return true;
80 }
81 if self.folding == 2 && matches!(byte, b' ' | b'\t') {
82 self.folding = 0;
83 return true;
84 }
85 return false;
86 }
87 match self.context {
88 Context::Comment { depth, escaped } => {
89 if matches!(byte, b'\r' | b'\n') {
90 self.context = Context::Comment {
91 depth,
92 escaped: false,
93 };
94 self.folding = if byte == b'\r' { 1 } else { 2 };
95 } else if escaped {
96 self.context = Context::Comment {
97 depth,
98 escaped: false,
99 };
100 } else if byte == b'\\' {
101 self.context = Context::Comment {
102 depth,
103 escaped: true,
104 };
105 } else if byte == b'(' {
106 let Some(depth) = depth.checked_add(1) else {
107 return false;
108 };
109 self.context = Context::Comment {
110 depth,
111 escaped: false,
112 };
113 } else if byte == b')' {
114 self.context = if depth == 1 {
115 Context::Normal
116 } else {
117 Context::Comment {
118 depth: depth - 1,
119 escaped: false,
120 }
121 };
122 }
123 true
124 }
125 Context::Quoted { escaped } => {
126 if matches!(byte, b'\r' | b'\n') {
127 self.context = Context::Quoted { escaped: false };
128 self.folding = if byte == b'\r' { 1 } else { 2 };
129 } else if escaped {
130 self.context = Context::Quoted { escaped: false };
131 } else if byte == b'\\' {
132 self.context = Context::Quoted { escaped: true };
133 } else if byte == b'"' {
134 self.context = Context::Normal;
135 self.phase = Phase::AfterValue;
136 }
137 true
138 }
139 Context::Normal => self.normal(byte),
140 }
141 }
142
143 fn normal(&mut self, byte: u8) -> bool {
144 match byte {
145 b' ' | b'\t' => {
146 self.gap();
147 true
148 }
149 b'\r' | b'\n' => {
150 self.gap();
151 self.folding = if byte == b'\r' { 1 } else { 2 };
155 true
156 }
157 b'(' => {
158 self.gap();
159 self.context = Context::Comment {
160 depth: 1,
161 escaped: false,
162 };
163 true
164 }
165 b'"' if matches!(self.phase, Phase::ValueStart) => {
166 self.context = Context::Quoted { escaped: false };
167 true
168 }
169 b'/' if matches!(self.phase, Phase::Type | Phase::Slash) => {
170 self.phase = Phase::SubtypeStart;
171 true
172 }
173 b';' if matches!(
174 self.phase,
175 Phase::Subtype | Phase::Parameters | Phase::Value | Phase::AfterValue
176 ) =>
177 {
178 self.phase = Phase::AttributeStart;
179 true
180 }
181 b'=' if matches!(self.phase, Phase::Attribute | Phase::Equals) => {
182 self.phase = Phase::ValueStart;
183 true
184 }
185 _ if token(byte) => {
186 self.phase = match self.phase {
187 Phase::TypeStart | Phase::Type => Phase::Type,
188 Phase::SubtypeStart | Phase::Subtype => Phase::Subtype,
189 Phase::AttributeStart | Phase::Attribute => Phase::Attribute,
190 Phase::ValueStart | Phase::Value => Phase::Value,
191 _ => return false,
192 };
193 true
194 }
195 _ => false,
196 }
197 }
198
199 fn gap(&mut self) {
200 self.phase = match self.phase {
201 Phase::Type => Phase::Slash,
202 Phase::Subtype => Phase::Parameters,
203 Phase::Attribute => Phase::Equals,
204 Phase::Value => Phase::AfterValue,
205 other => other,
206 };
207 }
208
209 pub fn feed<W: Work>(
213 &mut self,
214 value: impl AsRef<[u8]>,
215 execution: &W,
216 ) -> Result<bool, MemoryError> {
217 let value = value.as_ref();
218 let cost = u64::try_from(value.len())
221 .ok()
222 .and_then(|n| n.checked_mul(4))
223 .ok_or(MemoryError::WorkExceeded)?;
224 execution.charge_work(cost)?;
225 if !self.valid {
226 return Ok(false);
227 }
228 for &byte in value {
229 if !self.byte(byte) {
230 self.valid = false;
231 return Ok(false);
232 }
233 }
234 Ok(true)
235 }
236
237 #[must_use]
239 pub fn complete(&self) -> bool {
240 self.valid
241 && self.folding == 0
242 && matches!(self.context, Context::Normal)
243 && matches!(
244 self.phase,
245 Phase::Subtype | Phase::Parameters | Phase::Value | Phase::AfterValue
246 )
247 }
248}
249
250fn token(byte: u8) -> bool {
251 matches!(byte, 33..=126)
252 && !matches!(
253 byte,
254 b'(' | b')'
255 | b'<'
256 | b'>'
257 | b'@'
258 | b','
259 | b';'
260 | b':'
261 | b'\\'
262 | b'"'
263 | b'/'
264 | b'['
265 | b']'
266 | b'?'
267 | b'='
268 )
269}
270
271impl<const XML_NORMALIZED: bool> Default for Syntax<XML_NORMALIZED> {
272 fn default() -> Self {
273 Self::new()
274 }
275}