1use core::fmt;
5
6use crate::error::{FormError, Location, SyntaxError};
7
8pub const MAX_BOUNDARY_LEN: usize = 70;
10
11pub const MAX_DELIMITER_LEN: usize = 74;
13
14const fn is_bchar(b: u8) -> bool {
16 b.is_ascii_alphanumeric()
17 || matches!(
18 b,
19 b'\''
20 | b'('
21 | b')'
22 | b'+'
23 | b'_'
24 | b','
25 | b'-'
26 | b'.'
27 | b'/'
28 | b':'
29 | b'='
30 | b'?'
31 | b' '
32 )
33}
34
35#[must_use]
55#[derive(Clone, Copy, PartialEq, Eq)]
56pub struct Boundary {
57 delimiter: [u8; MAX_DELIMITER_LEN],
58 fail: [u8; MAX_DELIMITER_LEN],
59 len: u8,
60}
61
62impl Boundary {
63 pub fn new(boundary: &[u8]) -> Result<Boundary, FormError> {
77 let invalid =
78 |at: usize| FormError::syntax(SyntaxError::InvalidBoundary, Location::at(at as u64));
79 if boundary.is_empty() {
80 return Err(invalid(0));
81 }
82 if boundary.len() > MAX_BOUNDARY_LEN {
83 return Err(invalid(MAX_BOUNDARY_LEN));
84 }
85 if let Some(at) = boundary.iter().position(|&b| !is_bchar(b)) {
86 return Err(invalid(at));
87 }
88 if boundary.last() == Some(&b' ') {
89 return Err(invalid(boundary.len().wrapping_sub(1)));
90 }
91 if boundary.first() == Some(&b'\'') {
92 return Err(invalid(0));
96 }
97 if boundary.last() == Some(&b'\'') {
98 return Err(invalid(boundary.len().wrapping_sub(1)));
99 }
100 let mut delimiter = [0u8; MAX_DELIMITER_LEN];
101 let len = boundary.len().wrapping_add(4);
102 if let Some(head) = delimiter.get_mut(..4) {
103 head.copy_from_slice(b"\r\n--");
104 }
105 if let Some(rest) = delimiter.get_mut(4..len) {
106 rest.copy_from_slice(boundary);
107 }
108 let fail = failure_table(&delimiter, len);
109 Ok(Boundary {
110 delimiter,
111 fail,
112 len: len as u8,
113 })
114 }
115
116 #[cfg(feature = "random")]
133 #[cfg_attr(docsrs, doc(cfg(feature = "random")))]
134 pub fn generate() -> Result<Boundary, FormError> {
135 let hex = crate::random::random_hex()?;
136 let mut name = [0u8; 41];
137 if let Some(head) = name.get_mut(..9) {
138 head.copy_from_slice(b"form-net-");
139 }
140 if let Some(tail) = name.get_mut(9..) {
141 tail.copy_from_slice(&hex);
142 }
143 Boundary::new(&name)
144 }
145
146 pub fn parse(content_type: &[u8]) -> Result<Boundary, FormError> {
163 match crate::kind::FormKind::detect(content_type)? {
164 crate::kind::FormKind::Multipart(b) => Ok(b),
165 crate::kind::FormKind::Urlencoded => Err(FormError::UnsupportedMediaType),
166 }
167 }
168
169 #[must_use]
178 pub fn as_bytes(&self) -> &[u8] {
179 self.delimiter.get(4..usize::from(self.len)).unwrap_or(&[])
180 }
181
182 #[must_use]
191 pub fn delimiter(&self) -> &[u8] {
192 self.delimiter.get(..usize::from(self.len)).unwrap_or(&[])
193 }
194
195 #[inline]
198 pub(crate) fn step(&self, mut k: usize, c: u8) -> usize {
199 #[cfg(test)]
200 tests::count_visits(1);
201 loop {
202 if self.delimiter.get(k) == Some(&c) {
203 return k.wrapping_add(1);
204 }
205 if k == 0 {
206 return 0;
207 }
208 k = usize::from(*self.fail.get(k.wrapping_sub(1)).unwrap_or(&0));
209 }
210 }
211
212 pub(crate) fn len(&self) -> usize {
213 usize::from(self.len)
214 }
215
216 #[inline]
222 pub(crate) fn bare_step(&self, m: usize, c: u8) -> usize {
223 if m > 0 && self.delimiter.get(m.wrapping_add(1)) == Some(&c) {
224 m.wrapping_add(1)
225 } else {
226 usize::from(crate::scan::is_break(c))
227 }
228 }
229
230 pub(crate) fn run(&self, mut k: usize, mut m: usize, hay: &[u8]) -> (usize, usize, Hit) {
249 let len = self.len();
250 let bare = len.wrapping_sub(1);
251 let delimiter = self.delimiter();
252 let dashes = delimiter.get(2..).unwrap_or(&[]);
253 let mut i = 0usize;
254 if k == 1 {
255 let Some(&c) = hay.first() else {
258 return (k, m, Hit::None);
259 };
260 k = self.step(k, c);
261 m = self.bare_step(m, c);
262 i = 1;
263 if k == 1 {
264 (i, k, m) = (0, 0, 0);
266 }
267 }
268 if m > 0 {
269 let need = delimiter.get(m.wrapping_add(1)..).unwrap_or(&[]);
272 let rest = hay.get(i..).unwrap_or(&[]);
273 let same = shared(need, rest);
274 if same == need.len() {
275 let end = i.wrapping_add(same);
276 return if k > 0 {
277 (len, 0, Hit::Delimiter(end))
278 } else {
279 (0, bare, Hit::BareBreak(end))
280 };
281 }
282 if same == rest.len() {
283 let k = if k > 0 { k.wrapping_add(same) } else { 0 };
285 return (k, m.wrapping_add(same), Hit::None);
286 }
287 i = i.wrapping_add(same);
288 }
289 while i < hay.len() {
290 let at = crate::scan::find_break_dash(hay, i);
291 if at >= hay.len() {
292 break;
293 }
294 let start = at.wrapping_add(1);
295 let after_cr = hay.get(at) == Some(&b'\n')
296 && at > 0
297 && hay.get(at.wrapping_sub(1)) == Some(&b'\r');
298 let rest = hay.get(start..).unwrap_or(&[]);
299 let same = shared(dashes, rest);
300 if same == dashes.len() {
301 let end = start.wrapping_add(same);
302 return if after_cr {
303 (len, 0, Hit::Delimiter(end))
304 } else {
305 (0, bare, Hit::BareBreak(end))
306 };
307 }
308 if same == rest.len() {
309 let k = if after_cr {
312 same.wrapping_add(2)
313 } else {
314 usize::from(same == 0 && hay.get(at) == Some(&b'\r'))
315 };
316 return (k, same.wrapping_add(1), Hit::None);
317 }
318 i = start.wrapping_add(same);
319 }
320 (0, 0, Hit::None)
321 }
322}
323
324#[inline]
326pub(crate) fn shared(a: &[u8], b: &[u8]) -> usize {
327 let n = crate::scan::common_prefix(a, b);
328 #[cfg(test)]
329 tests::count_visits(n.wrapping_add(1));
330 n
331}
332
333#[derive(Clone, Copy, Debug, PartialEq, Eq)]
335pub(crate) enum Hit {
336 None,
338 Delimiter(usize),
340 BareBreak(usize),
343}
344
345fn failure_table(d: &[u8; MAX_DELIMITER_LEN], len: usize) -> [u8; MAX_DELIMITER_LEN] {
348 let mut fail = [0u8; MAX_DELIMITER_LEN];
349 let mut k = 0usize;
350 for i in 1..len {
351 let c = *d.get(i).unwrap_or(&0);
352 while k > 0 && d.get(k) != Some(&c) {
353 k = usize::from(*fail.get(k.wrapping_sub(1)).unwrap_or(&0));
354 }
355 if d.get(k) == Some(&c) {
356 k = k.wrapping_add(1);
357 }
358 if let Some(slot) = fail.get_mut(i) {
359 *slot = k as u8;
360 }
361 }
362 fail
363}
364
365impl fmt::Debug for Boundary {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 let text = core::str::from_utf8(self.as_bytes()).unwrap_or("");
369 f.debug_tuple("Boundary").field(&text).finish()
370 }
371}
372
373#[cfg(test)]
374pub(crate) mod tests {
375 use super::*;
376 use alloc::vec::Vec;
377 use core::cell::Cell;
378
379 std::thread_local! {
380 static VISITS: Cell<u64> = const { Cell::new(0) };
381 }
382
383 pub(crate) fn count_visits(n: usize) {
387 VISITS.with(|v| v.set(v.get() + n as u64));
388 }
389
390 #[cfg(feature = "alloc")]
391 pub(crate) fn visits() -> u64 {
392 VISITS.with(Cell::get)
393 }
394
395 #[cfg(feature = "alloc")]
398 fn visits_for(body: &[u8], chunk: usize) -> u64 {
399 use crate::{Event, Limits, MultipartParser, Policy};
400 let limits = Limits::DEFAULT
401 .with_max_body_bytes(64 << 20)
402 .with_max_file_bytes(64 << 20);
403 let mut p = MultipartParser::new(
404 Boundary::new(b"boundary").unwrap(),
405 &limits,
406 &Policy::DEFAULT,
407 )
408 .unwrap();
409 let before = visits();
410 let chunks: Vec<&[u8]> = body.chunks(chunk).collect();
411 for (i, c) in chunks.iter().enumerate() {
412 let mut rest: &[u8] = c;
413 loop {
414 let step = p.parse(rest, i + 1 == chunks.len()).unwrap();
415 rest = &rest[step.consumed..];
416 if matches!(step.event, Event::NeedInput | Event::End) {
417 break;
418 }
419 }
420 }
421 visits() - before
422 }
423
424 #[cfg(feature = "alloc")]
425 #[test]
426 fn test_matcher_visits_are_linear_for_adversarial_bodies() {
427 let head = b"--boundary\r\nContent-Disposition: form-data; name=f; filename=a\r\n\r\n";
428 let shapes: [&[u8]; 4] = [b"\r", b"\r\n--boundar", b"\r\n-", b"-"];
429 for shape in shapes {
430 for chunk in [1usize, 7, 4096] {
431 let mut body = head.to_vec();
432 while body.len() < head.len() + 64 * 1024 {
433 body.extend_from_slice(shape);
434 }
435 let data = (body.len() - head.len()) as u64;
436 body.extend_from_slice(b"\r\n--boundary--");
437 let v = visits_for(&body, chunk);
438 let bound = 2 * data + (body.len() / chunk + 1) as u64 * 16 + 64;
442 assert!(
443 v <= bound,
444 "{shape:?} chunk {chunk}: {v} visits for {data} bytes"
445 );
446 }
447 }
448 }
449
450 #[test]
451 fn test_boundary_every_byte_value() {
452 for b in 0u8..=255 {
453 let r = Boundary::new(&[b'a', b, b'z']);
454 assert_eq!(r.is_ok(), is_bchar(b), "byte {b:#x}");
455 }
456 }
457
458 #[test]
459 fn test_boundary_lengths_and_trailing_space() {
460 assert!(Boundary::new(b"").is_err());
461 assert!(Boundary::new(b"a").is_ok());
462 assert!(Boundary::new(&[b'a'; 69]).is_ok());
463 assert!(Boundary::new(&[b'a'; 70]).is_ok());
464 assert!(Boundary::new(&[b'a'; 71]).is_err());
465 assert!(Boundary::new(b"a ").is_err());
466 assert!(Boundary::new(b"a b").is_ok());
467 assert!(Boundary::new(b" ").is_err());
468 }
469
470 #[test]
471 fn test_failure_table_matches_definition() {
472 for raw in [&b"a"[..], b"aaaa", b"-", b"--", b"-a-", b"---", b"a-b--a"] {
473 let b = Boundary::new(raw).unwrap();
474 let d = b.delimiter();
475 for i in 0..d.len() {
476 let best = (0..=i)
477 .rev()
478 .find(|&l| l <= i && d[..l] == d[i + 1 - l..=i])
479 .unwrap();
480 assert_eq!(usize::from(b.fail[i]), best, "{raw:?} at {i}");
481 }
482 }
483 }
484
485 fn naive_hit(b: &Boundary, hay: &[u8]) -> Hit {
488 let d = b.delimiter();
489 let dashes = &d[2..];
490 for end in 0..=hay.len() {
491 if end >= d.len() && &hay[end - d.len()..end] == d {
492 return Hit::Delimiter(end);
493 }
494 if end > dashes.len() && &hay[end - dashes.len()..end] == dashes {
495 let p = end - dashes.len() - 1;
496 if hay[p] == b'\r' || hay[p] == b'\n' {
497 return Hit::BareBreak(end);
498 }
499 }
500 }
501 Hit::None
502 }
503
504 fn chunked_hit(b: &Boundary, chunks: &[&[u8]]) -> Hit {
507 let (mut k, mut m, mut base) = (0, 0, 0);
508 for chunk in chunks {
509 let (k2, m2, hit) = b.run(k, m, chunk);
510 match hit {
511 Hit::Delimiter(e) => return Hit::Delimiter(base + e),
512 Hit::BareBreak(e) => return Hit::BareBreak(base + e),
513 Hit::None => (k, m, base) = (k2, m2, base + chunk.len()),
514 }
515 }
516 Hit::None
517 }
518
519 #[test]
520 fn test_run_finds_like_naive_and_carries_state() {
521 let b = Boundary::new(b"-ab").unwrap();
522 let d = b.delimiter().to_vec();
523 let mut hay = Vec::new();
524 hay.extend_from_slice(b"x\r\n--a\r\n\r\n--\r\n--");
525 hay.extend_from_slice(&d);
526 hay.extend_from_slice(b"tail");
527 let expected = naive_hit(&b, &hay);
528 assert!(matches!(expected, Hit::Delimiter(_)));
529 assert_eq!(chunked_hit(&b, &[&hay]), expected);
530 for split in 0..hay.len() {
531 let (x, y) = hay.split_at(split);
532 assert_eq!(chunked_hit(&b, &[x, y]), expected, "split {split}");
533 }
534 }
535
536 #[test]
540 fn test_bare_breaks_and_delimiters_match_naive_at_every_split() {
541 let mut seed = 11u64;
542 for boundary in [&b"-ab"[..], b"--", b"x", b"ab-"] {
543 let b = Boundary::new(boundary).unwrap();
544 for len in 0..40 {
545 for _ in 0..60 {
546 let hay: Vec<u8> = (0..len)
547 .map(|_| {
548 seed ^= seed << 13;
549 seed ^= seed >> 7;
550 seed ^= seed << 17;
551 b"\r\n--abx"[(seed % 7) as usize]
552 })
553 .collect();
554 let expected = naive_hit(&b, &hay);
555 assert_eq!(chunked_hit(&b, &[&hay]), expected, "{hay:?}");
556 for split in 0..=hay.len() {
557 let (x, y) = hay.split_at(split);
558 assert_eq!(chunked_hit(&b, &[x, y]), expected, "{hay:?} at {split}");
559 }
560 let bytes: Vec<&[u8]> = hay.chunks(1).collect();
561 assert_eq!(chunked_hit(&b, &bytes), expected, "{hay:?} bytewise");
562 }
563 }
564 }
565 }
566
567 #[test]
570 fn test_run_leaves_the_automata_states_at_every_split() {
571 let mut seed = 5u64;
572 for boundary in [&b"-ab"[..], b"--", b"x", b"ab-", b"abcdefghijklmnop"] {
573 let b = Boundary::new(boundary).unwrap();
574 let bare = b.len() - 1;
575 let hay_bytes: &[u8] = &[b'\r', b'\n', b'-', b'-', boundary[0], b'x'];
576 for len in 0..48 {
577 for _ in 0..40 {
578 let mut hay: Vec<u8> = (0..len)
579 .map(|_| {
580 seed ^= seed << 13;
581 seed ^= seed >> 7;
582 seed ^= seed << 17;
583 hay_bytes[(seed % 6) as usize]
584 })
585 .collect();
586 let d = b.delimiter();
588 hay.extend_from_slice(&d[..len % d.len()]);
589 if !matches!(naive_hit(&b, &hay), Hit::None) {
590 continue;
591 }
592 let (mut k, mut m) = (0, 0);
593 for &c in &hay {
594 k = b.step(k, c);
595 m = b.bare_step(m, c);
596 assert!(k < b.len() && m < bare);
597 }
598 assert_eq!(&b.run(0, 0, &hay), &(k, m, Hit::None), "{hay:?}");
599 for split in 0..=hay.len() {
600 let (x, y) = hay.split_at(split);
601 let (k1, m1, _) = b.run(0, 0, x);
602 assert_eq!(&b.run(k1, m1, y), &(k, m, Hit::None), "{hay:?} at {split}");
603 }
604 }
605 }
606 }
607 }
608
609 #[test]
610 fn test_parse_reports_unsupported_for_urlencoded() {
611 assert_eq!(
612 Boundary::parse(b"application/x-www-form-urlencoded"),
613 Err(FormError::UnsupportedMediaType)
614 );
615 }
616}