swf: Take advantage of `ClipEventFlag` bit pattern

Re-number the `ClipEventFlag` enum members to match how they
actually appear in a SWF. This allows much simpler read/write
operations.

Also, gracefully handle malformed ClipActions that are only 1 or 0
bytes, as it seems that Flash accepts those too.
This commit is contained in:
relrelb 2021-08-27 14:10:53 +03:00 committed by relrelb
parent 945bce4a85
commit 55ffda9c97
3 changed files with 40 additions and 84 deletions

View File

@ -2027,7 +2027,7 @@ impl<'a> Reader<'a> {
fn read_clip_actions(&mut self) -> Result<Vec<ClipAction<'a>>> { fn read_clip_actions(&mut self) -> Result<Vec<ClipAction<'a>>> {
self.read_u16()?; // Must be 0 self.read_u16()?; // Must be 0
self.read_clip_event_flags()?; // All event flags self.read_clip_event_flags(); // All event flags
let mut clip_actions = vec![]; let mut clip_actions = vec![];
while let Some(clip_action) = self.read_clip_action()? { while let Some(clip_action) = self.read_clip_action()? {
clip_actions.push(clip_action); clip_actions.push(clip_action);
@ -2036,7 +2036,7 @@ impl<'a> Reader<'a> {
} }
fn read_clip_action(&mut self) -> Result<Option<ClipAction<'a>>> { fn read_clip_action(&mut self) -> Result<Option<ClipAction<'a>>> {
let events = self.read_clip_event_flags()?; let events = self.read_clip_event_flags();
if events.is_empty() { if events.is_empty() {
Ok(None) Ok(None)
} else { } else {
@ -2058,51 +2058,21 @@ impl<'a> Reader<'a> {
} }
} }
fn read_clip_event_flags(&mut self) -> Result<ClipEventFlag> { fn read_clip_event_flags(&mut self) -> ClipEventFlag {
// TODO: Switch to a bitset. // There are SWFs in the wild with malformed final ClipActions that is only 2 bytes
let mut event_list = ClipEventFlag::empty(); // instead of 4 bytes (#2899). Handle this gracefully to allow the tag to run.
// TODO: We may need a more general way to handle truncated tags, since this has
let flags = self.read_u8()?; // occurred in a few different places.
event_list.set(ClipEventFlag::KEY_UP, flags & 0b1000_0000 != 0); let bits = if self.version >= 6 {
event_list.set(ClipEventFlag::KEY_DOWN, flags & 0b0100_0000 != 0); self.read_u32().unwrap_or_default()
event_list.set(ClipEventFlag::MOUSE_UP, flags & 0b0010_0000 != 0);
event_list.set(ClipEventFlag::MOUSE_DOWN, flags & 0b0001_0000 != 0);
event_list.set(ClipEventFlag::MOUSE_MOVE, flags & 0b0000_1000 != 0);
event_list.set(ClipEventFlag::UNLOAD, flags & 0b0000_0100 != 0);
event_list.set(ClipEventFlag::ENTER_FRAME, flags & 0b0000_0010 != 0);
event_list.set(ClipEventFlag::LOAD, flags & 0b0000_0001 != 0);
if self.version > 5 {
// There are SWFs in the wild with malformed final ClipActions that is only two bytes
// instead of four bytes (see #2899). Handle this gracefully to allow the tag to run.
// TODO: We may need a more general way to handle truncated tags, since this has
// occurred in a few different places.
// Allow for only two bytes in the clip action tag.
let flags = self.read_u8().unwrap_or_default();
let flags2 = self.read_u8().unwrap_or_default();
let _ = self.read_u8();
event_list.set(ClipEventFlag::DRAG_OVER, flags & 0b1000_0000 != 0);
event_list.set(ClipEventFlag::ROLL_OUT, flags & 0b0100_0000 != 0);
event_list.set(ClipEventFlag::ROLL_OVER, flags & 0b0010_0000 != 0);
event_list.set(ClipEventFlag::RELEASE_OUTSIDE, flags & 0b0001_0000 != 0);
event_list.set(ClipEventFlag::RELEASE, flags & 0b0000_1000 != 0);
event_list.set(ClipEventFlag::PRESS, flags & 0b0000_0100 != 0);
event_list.set(ClipEventFlag::INITIALIZE, flags & 0b0000_0010 != 0);
event_list.set(ClipEventFlag::DATA, flags & 0b0000_0001 != 0);
// Construct was only added in SWF7, but it's not version-gated;
// Construct events will still fire in SWF6 in a v7+ player. (#1424)
event_list.set(ClipEventFlag::CONSTRUCT, flags2 & 0b0000_0100 != 0);
event_list.set(ClipEventFlag::KEY_PRESS, flags2 & 0b0000_0010 != 0);
event_list.set(ClipEventFlag::DRAG_OUT, flags2 & 0b0000_0001 != 0);
} else { } else {
// SWF19 pp. 48-50: For SWFv5, the ClipEventFlags only had 2 bytes of flags, // SWF19 pp. 48-50: For SWFv5, the ClipEventFlags only had 2 bytes of flags,
// with the 2nd byte reserved (all 0). // with the 2nd byte reserved (all 0).
// This was expanded to 4 bytes in SWFv6. // This was expanded to 4 bytes in SWFv6.
self.read_u8()?; (self.read_u16().unwrap_or_default() as u8).into()
} };
Ok(event_list) ClipEventFlag::from_bits_truncate(bits)
} }
pub fn read_filter(&mut self) -> Result<Filter> { pub fn read_filter(&mut self) -> Result<Filter> {

View File

@ -742,25 +742,30 @@ bitflags! {
/// ///
/// [SWF19 pp.48-50 ClipEvent](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=50) /// [SWF19 pp.48-50 ClipEvent](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=50)
pub struct ClipEventFlag: u32 { pub struct ClipEventFlag: u32 {
const CONSTRUCT = 1 << 0; const LOAD = 1 << 0;
const DATA = 1 << 1; const ENTER_FRAME = 1 << 1;
const DRAG_OUT = 1 << 2; const UNLOAD = 1 << 2;
const DRAG_OVER = 1 << 3; const MOUSE_MOVE = 1 << 3;
const ENTER_FRAME = 1 << 4; const MOUSE_DOWN = 1 << 4;
const INITIALIZE = 1 << 5; const MOUSE_UP = 1 << 5;
const KEY_UP = 1 << 6; const KEY_DOWN = 1 << 6;
const KEY_DOWN = 1 << 7; const KEY_UP = 1 << 7;
const KEY_PRESS = 1 << 8;
const LOAD = 1 << 9; // Added in SWF6.
const MOUSE_UP = 1 << 10; const DATA = 1 << 8;
const MOUSE_DOWN = 1 << 11; const INITIALIZE = 1 << 9;
const MOUSE_MOVE = 1 << 12; const PRESS = 1 << 10;
const PRESS = 1 << 13; const RELEASE = 1 << 11;
const RELEASE_OUTSIDE = 1 << 12;
const ROLL_OVER = 1 << 13;
const ROLL_OUT = 1 << 14; const ROLL_OUT = 1 << 14;
const ROLL_OVER = 1 << 15; const DRAG_OVER = 1 << 15;
const RELEASE = 1 << 16; const DRAG_OUT = 1 << 16;
const RELEASE_OUTSIDE = 1 << 17; const KEY_PRESS = 1 << 17;
const UNLOAD = 1 << 18;
// Construct was only added in SWF7, but it's not version-gated;
// Construct events will still fire in SWF6 in a v7+ player (#1424).
const CONSTRUCT = 1 << 18;
} }
} }

View File

@ -2009,30 +2009,11 @@ impl<W: Write> Writer<W> {
fn write_clip_event_flags(&mut self, clip_events: ClipEventFlag) -> Result<()> { fn write_clip_event_flags(&mut self, clip_events: ClipEventFlag) -> Result<()> {
// TODO: Assert proper version. // TODO: Assert proper version.
let version = self.version; let bits = clip_events.bits();
let mut bits = self.bits(); if self.version >= 6 {
bits.write_bit(clip_events.contains(ClipEventFlag::KEY_UP))?; self.write_u32(bits)?;
bits.write_bit(clip_events.contains(ClipEventFlag::KEY_DOWN))?; } else {
bits.write_bit(clip_events.contains(ClipEventFlag::MOUSE_UP))?; self.write_u16((bits as u8).into())?;
bits.write_bit(clip_events.contains(ClipEventFlag::MOUSE_DOWN))?;
bits.write_bit(clip_events.contains(ClipEventFlag::MOUSE_MOVE))?;
bits.write_bit(clip_events.contains(ClipEventFlag::UNLOAD))?;
bits.write_bit(clip_events.contains(ClipEventFlag::ENTER_FRAME))?;
bits.write_bit(clip_events.contains(ClipEventFlag::LOAD))?;
bits.write_bit(clip_events.contains(ClipEventFlag::DRAG_OVER))?;
bits.write_bit(clip_events.contains(ClipEventFlag::ROLL_OUT))?;
bits.write_bit(clip_events.contains(ClipEventFlag::ROLL_OVER))?;
bits.write_bit(clip_events.contains(ClipEventFlag::RELEASE_OUTSIDE))?;
bits.write_bit(clip_events.contains(ClipEventFlag::RELEASE))?;
bits.write_bit(clip_events.contains(ClipEventFlag::PRESS))?;
bits.write_bit(clip_events.contains(ClipEventFlag::INITIALIZE))?;
bits.write_bit(clip_events.contains(ClipEventFlag::DATA))?;
if version >= 6 {
bits.write_ubits(5, 0)?;
bits.write_bit(clip_events.contains(ClipEventFlag::CONSTRUCT))?;
bits.write_bit(clip_events.contains(ClipEventFlag::KEY_PRESS))?;
bits.write_bit(clip_events.contains(ClipEventFlag::DRAG_OUT))?;
bits.write_ubits(8, 0)?;
} }
Ok(()) Ok(())
} }