1use structured_asn1::memory::{MemoryError, Work};
4
5mod headers;
6pub use headers::media_type_or_headers;
7mod essence;
8pub use essence::Essences;
9
10trait Head {
11 fn cost(&self) -> Result<u64, MemoryError>;
12 fn type_byte(&mut self, byte: u8);
13 fn subtype_byte(&mut self, byte: u8);
14}
15impl Head for () {
16 fn cost(&self) -> Result<u64, MemoryError> {
17 Ok(0)
18 }
19 fn type_byte(&mut self, _: u8) {}
20 fn subtype_byte(&mut self, _: u8) {}
21}
22
23#[derive(Clone, Copy)]
24enum Phase {
25 TypeStart,
26 Type,
27 Slash,
28 SubtypeStart,
29 Subtype,
30 Parameters,
31 AttributeStart,
32 Attribute,
33 Equals,
34 ValueStart,
35 Value,
36 AfterValue,
37}
38
39#[derive(Clone, Copy)]
40enum Context {
41 Normal,
42 Comment { depth: usize, escaped: bool },
43 Quoted { escaped: bool },
44}
45
46pub struct Syntax<const XML_NORMALIZED: bool = false> {
64 valid: bool,
65 phase: Phase,
66 context: Context,
67 folding: u8,
68}
69
70impl<const XML_NORMALIZED: bool> Syntax<XML_NORMALIZED> {
71 #[must_use]
73 pub const fn new() -> Self {
74 Self {
75 valid: true,
76 phase: Phase::TypeStart,
77 context: Context::Normal,
78 folding: 0,
79 }
80 }
81
82 fn byte<H: Head>(&mut self, byte: u8, head: &mut H) -> bool {
88 if !byte.is_ascii() || (byte == b'\n' && self.folding != 1 && !XML_NORMALIZED) {
89 return false;
90 }
91 if self.folding != 0 {
92 if self.folding == 1 && byte == b'\n' {
93 self.folding = 2;
94 return true;
95 }
96 if self.folding == 2 && matches!(byte, b' ' | b'\t') {
97 self.folding = 0;
98 return true;
99 }
100 return false;
101 }
102 match self.context {
103 Context::Comment { depth, escaped } => {
104 if matches!(byte, b'\r' | b'\n') {
105 self.context = Context::Comment {
106 depth,
107 escaped: false,
108 };
109 self.folding = if byte == b'\r' { 1 } else { 2 };
110 } else if escaped {
111 self.context = Context::Comment {
112 depth,
113 escaped: false,
114 };
115 } else if byte == b'\\' {
116 self.context = Context::Comment {
117 depth,
118 escaped: true,
119 };
120 } else if byte == b'(' {
121 let Some(depth) = depth.checked_add(1) else {
122 return false;
123 };
124 self.context = Context::Comment {
125 depth,
126 escaped: false,
127 };
128 } else if byte == b')' {
129 self.context = if depth == 1 {
130 Context::Normal
131 } else {
132 Context::Comment {
133 depth: depth - 1,
134 escaped: false,
135 }
136 };
137 }
138 true
139 }
140 Context::Quoted { escaped } => {
141 if matches!(byte, b'\r' | b'\n') {
142 self.context = Context::Quoted { escaped: false };
143 self.folding = if byte == b'\r' { 1 } else { 2 };
144 } else if escaped {
145 self.context = Context::Quoted { escaped: false };
146 } else if byte == b'\\' {
147 self.context = Context::Quoted { escaped: true };
148 } else if byte == b'"' {
149 self.context = Context::Normal;
150 self.phase = Phase::AfterValue;
151 }
152 true
153 }
154 Context::Normal => self.normal(byte, head),
155 }
156 }
157
158 fn normal<H: Head>(&mut self, byte: u8, head: &mut H) -> bool {
159 match byte {
160 b' ' | b'\t' => {
161 self.gap();
162 true
163 }
164 b'\r' | b'\n' => {
165 self.gap();
166 self.folding = if byte == b'\r' { 1 } else { 2 };
170 true
171 }
172 b'(' => {
173 self.gap();
174 self.context = Context::Comment {
175 depth: 1,
176 escaped: false,
177 };
178 true
179 }
180 b'"' if matches!(self.phase, Phase::ValueStart) => {
181 self.context = Context::Quoted { escaped: false };
182 true
183 }
184 b'/' if matches!(self.phase, Phase::Type | Phase::Slash) => {
185 self.phase = Phase::SubtypeStart;
186 true
187 }
188 b';' if matches!(
189 self.phase,
190 Phase::Subtype | Phase::Parameters | Phase::Value | Phase::AfterValue
191 ) =>
192 {
193 self.phase = Phase::AttributeStart;
194 true
195 }
196 b'=' if matches!(self.phase, Phase::Attribute | Phase::Equals) => {
197 self.phase = Phase::ValueStart;
198 true
199 }
200 _ if token(byte) => {
201 self.phase = match self.phase {
202 Phase::TypeStart | Phase::Type => {
203 head.type_byte(byte);
204 Phase::Type
205 }
206 Phase::SubtypeStart | Phase::Subtype => {
207 head.subtype_byte(byte);
208 Phase::Subtype
209 }
210 Phase::AttributeStart | Phase::Attribute => Phase::Attribute,
211 Phase::ValueStart | Phase::Value => Phase::Value,
212 _ => return false,
213 };
214 true
215 }
216 _ => false,
217 }
218 }
219
220 fn gap(&mut self) {
221 self.phase = match self.phase {
222 Phase::Type => Phase::Slash,
223 Phase::Subtype => Phase::Parameters,
224 Phase::Attribute => Phase::Equals,
225 Phase::Value => Phase::AfterValue,
226 other => other,
227 };
228 }
229
230 pub fn feed<W: Work>(
234 &mut self,
235 value: impl AsRef<[u8]>,
236 execution: &W,
237 ) -> Result<bool, MemoryError> {
238 self.scan(value.as_ref(), execution, &mut ())
239 }
240
241 fn scan<W: Work, H: Head>(
242 &mut self,
243 value: &[u8],
244 execution: &W,
245 head: &mut H,
246 ) -> Result<bool, MemoryError> {
247 let per_byte = head
250 .cost()?
251 .checked_add(4)
252 .ok_or(MemoryError::WorkExceeded)?;
253 let cost = u64::try_from(value.len())
254 .ok()
255 .and_then(|n| n.checked_mul(per_byte))
256 .ok_or(MemoryError::WorkExceeded)?;
257 execution.charge_work(cost)?;
258 if !self.valid {
259 return Ok(false);
260 }
261 for &byte in value {
262 if !self.byte(byte, head) {
263 self.valid = false;
264 return Ok(false);
265 }
266 }
267 Ok(true)
268 }
269
270 #[must_use]
272 pub fn complete(&self) -> bool {
273 self.valid
274 && self.folding == 0
275 && matches!(self.context, Context::Normal)
276 && matches!(
277 self.phase,
278 Phase::Subtype | Phase::Parameters | Phase::Value | Phase::AfterValue
279 )
280 }
281}
282
283fn token(byte: u8) -> bool {
284 matches!(byte, 33..=126)
285 && !matches!(
286 byte,
287 b'(' | b')'
288 | b'<'
289 | b'>'
290 | b'@'
291 | b','
292 | b';'
293 | b':'
294 | b'\\'
295 | b'"'
296 | b'/'
297 | b'['
298 | b']'
299 | b'?'
300 | b'='
301 )
302}
303
304impl<const XML_NORMALIZED: bool> Default for Syntax<XML_NORMALIZED> {
305 fn default() -> Self {
306 Self::new()
307 }
308}