swf_parser/streaming/
tag.rs

1use crate::complete::tag::parse_tag_body;
2use nom::number::streaming::{le_u16 as parse_le_u16, le_u32 as parse_le_u32};
3use nom::IResult as NomResult;
4use std::convert::TryFrom;
5use swf_types as ast;
6
7/// Represents an error caused by incomplete input.
8#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub(crate) enum StreamingTagError {
10  /// Indicates that the input is not long enough to read the tag header.
11  ///
12  /// A header is `1`, `2` or `6` bytes long.
13  /// Parsing with an input of 6 bytes or more guarantees that this error
14  /// will not occur.
15  IncompleteHeader,
16
17  /// Indicates that the input is not long enough to read the full tag
18  /// (header and body).
19  ///
20  /// The value indicates the full length of the tag. Parsing with an input at
21  /// least (or exactly) this long guarantees that the tag will be parsed.
22  IncompleteTag(usize),
23}
24
25/// Parses the tag at the start of the (possibly incomplete) input.
26///
27/// In case of success, returns the remaining input and `Tag`.
28/// In case of error, returns the original input and error description.
29pub(crate) fn parse_tag(input: &[u8], swf_version: u8) -> Result<(&[u8], Option<ast::Tag>), StreamingTagError> {
30  let base_input = input; // Keep original input to compute lengths.
31  let (input, header) = match parse_tag_header(input) {
32    Ok(ok) => ok,
33    Err(_) => return Err(StreamingTagError::IncompleteHeader),
34  };
35
36  // `EndOfTags`
37  if header.code == 0 {
38    return Ok((&[], None));
39  }
40
41  let body_len = usize::try_from(header.length).unwrap();
42  if input.len() < body_len {
43    let header_len = base_input.len() - input.len();
44    let tag_len = header_len + body_len;
45    return Err(StreamingTagError::IncompleteTag(tag_len));
46  }
47  let (tag_body, input) = input.split_at(body_len);
48  let tag = parse_tag_body(tag_body, header.code, swf_version);
49  Ok((input, Some(tag)))
50}
51
52pub(crate) fn parse_tag_header(input: &[u8]) -> NomResult<&[u8], ast::TagHeader> {
53  // TODO: More test samples to pin down how Adobe's player handles this case (header starting with a NUL byte).
54  // if input.len() > 0 && input[0] == 0 {
55  //   // If the first byte is `NUL`, treat it as an end-of-tags marker.
56  //   // This is not documented but seems to be how SWF works in practice
57  //   return Ok((&input[1..], ast::TagHeader { code: 0, length: 0}));
58  // }
59
60  let (input, code_and_length) = parse_le_u16(input)?;
61  let code = code_and_length >> 6;
62  let max_length = (1 << 6) - 1;
63  let length = code_and_length & max_length;
64  let (input, length) = if length < max_length {
65    (input, u32::from(length))
66  } else {
67    debug_assert_eq!(length, max_length);
68    parse_le_u32(input)?
69  };
70
71  Ok((input, ast::TagHeader { code, length }))
72}