swf: Add custom error type for SWF/AVM1 parse errors

Improves the error handling for the swf crate:

 * Custom swf::error::Error type added to handle various errors
   in SWF parsing.
 * Invalid parsing of tags/AVM1 ops results in a Error::ParseError
   that can include info about the underlying failure.
 * Implement Display for these errors. Output descriptive
   names for the tag/opcode when it fails to parse.
 * Handle out of bounds reads in avm1::Reader::read_slice.
   Previously this would panic, now it returns an io::Error.

Closes #85.
This commit is contained in:
Mike Welsh 2019-10-09 16:20:31 -07:00
parent 40722dcef0
commit 3058b88011
7 changed files with 340 additions and 229 deletions

View File

@ -1,4 +1,4 @@
mod opcode;
pub(crate) mod opcode;
pub mod read;
pub mod types;
pub mod write;

View File

@ -2,8 +2,9 @@
use crate::avm1::opcode::OpCode;
use crate::avm1::types::*;
use crate::error::{Error, Result};
use crate::read::SwfRead;
use std::io::{Cursor, Error, ErrorKind, Result};
use std::io::Cursor;
#[allow(dead_code)]
pub struct Reader<'a> {
@ -37,11 +38,13 @@ impl<'a> Reader<'a> {
}
#[inline]
fn read_slice(&mut self, len: usize) -> &'a [u8] {
fn read_slice(&mut self, len: usize) -> Result<&'a [u8]> {
let pos = self.pos();
let slice = &self.inner.get_ref()[pos..pos + len];
self.inner.set_position(pos as u64 + len as u64);
slice
let slice = self.inner.get_ref().get(pos..pos + len).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Buffer underrun")
})?;
Ok(slice)
}
#[inline]
@ -60,15 +63,50 @@ impl<'a> Reader<'a> {
// TODO: What does Flash do on invalid UTF8?
// Do we silently let it pass?
// TODO: Verify ANSI for SWF 5 and earlier.
std::str::from_utf8(str_slice)
.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid string data"))
std::str::from_utf8(str_slice).map_err(|_| Error::invalid_data("Invalid string data"))
}
#[inline]
pub fn read_action(&mut self) -> Result<Option<Action<'a>>> {
use num_traits::FromPrimitive;
let (opcode, mut length) = self.read_opcode_and_length()?;
let (opcode, length) = self.read_opcode_and_length()?;
let start_pos = self.pos();
let action = self.read_op(opcode, &mut length);
if let Err(e) = action {
return Err(Error::avm1_parse_error_with_source(opcode, e));
}
// Verify that we parsed the correct amount of data.
let end_pos = start_pos + length;
let pos = self.pos();
if pos != end_pos {
// We incorrectly parsed this action.
// Re-sync to the expected end of the action and throw an error.
use std::convert::TryInto;
self.inner.set_position(end_pos.try_into().unwrap());
return Err(Error::avm1_parse_error(opcode));
}
action
}
pub fn read_opcode_and_length(&mut self) -> Result<(u8, usize)> {
let opcode = self.read_u8()?;
let length = if opcode >= 0x80 {
self.read_u16()? as usize
} else {
0
};
Ok((opcode, length))
}
/// Reads an action with the given opcode.
/// `length` is an in-out parameter and will be modified in the case of instructions
/// that contain sub-blocks of code, such as `DefineFunction`.
/// The `length` passed in should be the length excluding any sub-blocks.
/// The final `length` returned will be total length of the action, including sub-blocks.
#[inline]
fn read_op(&mut self, opcode: u8, length: &mut usize) -> Result<Option<Action<'a>>> {
use num_traits::FromPrimitive;
let action = if let Some(op) = OpCode::from_u8(opcode) {
match op {
OpCode::End => return Ok(None),
@ -97,8 +135,8 @@ impl<'a> Reader<'a> {
Action::ConstantPool(constants)
}
OpCode::Decrement => Action::Decrement,
OpCode::DefineFunction => self.read_define_function()?,
OpCode::DefineFunction2 => self.read_define_function_2()?,
OpCode::DefineFunction => self.read_define_function(length)?,
OpCode::DefineFunction2 => self.read_define_function_2(length)?,
OpCode::DefineLocal => Action::DefineLocal,
OpCode::DefineLocal2 => Action::DefineLocal2,
OpCode::Delete => Action::Delete,
@ -127,10 +165,9 @@ impl<'a> Reader<'a> {
1 => SendVarsMethod::Get,
2 => SendVarsMethod::Post,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Invalid HTTP method in ActionGetUrl2",
))
));
}
},
}
@ -181,7 +218,7 @@ impl<'a> Reader<'a> {
OpCode::Pop => Action::Pop,
OpCode::PreviousFrame => Action::PreviousFrame,
// TODO: Verify correct version for complex types.
OpCode::Push => self.read_push(length)?,
OpCode::Push => self.read_push(*length)?,
OpCode::PushDuplicate => Action::PushDuplicate,
OpCode::RandomNumber => Action::RandomNumber,
OpCode::RemoveSprite => Action::RemoveSprite,
@ -211,16 +248,17 @@ impl<'a> Reader<'a> {
OpCode::ToNumber => Action::ToNumber,
OpCode::ToString => Action::ToString,
OpCode::Trace => Action::Trace,
OpCode::Try => self.read_try()?,
OpCode::Try => self.read_try(length)?,
OpCode::TypeOf => Action::TypeOf,
OpCode::WaitForFrame => Action::WaitForFrame {
frame: self.read_u16()?,
num_actions_to_skip: self.read_u8()?,
},
OpCode::With => {
let code_length = self.read_u16()?;
let code_length = usize::from(self.read_u16()?);
*length += code_length;
Action::With {
actions: self.read_slice(code_length.into()),
actions: self.read_slice(code_length)?,
}
}
OpCode::WaitForFrame2 => Action::WaitForFrame2 {
@ -228,26 +266,16 @@ impl<'a> Reader<'a> {
},
}
} else {
self.read_unknown_action(opcode, length)?
self.read_unknown_action(opcode, *length)?
};
Ok(Some(action))
}
pub fn read_opcode_and_length(&mut self) -> Result<(u8, usize)> {
let opcode = self.read_u8()?;
let length = if opcode >= 0x80 {
self.read_u16()? as usize
} else {
0
};
Ok((opcode, length))
}
fn read_unknown_action(&mut self, opcode: u8, length: usize) -> Result<Action<'a>> {
Ok(Action::Unknown {
opcode,
data: self.read_slice(length),
data: self.read_slice(length)?,
})
}
@ -272,17 +300,12 @@ impl<'a> Reader<'a> {
7 => Value::Int(self.read_i32()?),
8 => Value::ConstantPool(self.read_u8()?.into()),
9 => Value::ConstantPool(self.read_u16()?),
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid value type in ActionPush",
))
}
_ => return Err(Error::invalid_data("Invalid value type in ActionPush")),
};
Ok(value)
}
fn read_define_function(&mut self) -> Result<Action<'a>> {
fn read_define_function(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
let name = self.read_c_string()?;
let num_params = self.read_u16()?;
let mut params = Vec::with_capacity(num_params as usize);
@ -290,15 +313,16 @@ impl<'a> Reader<'a> {
params.push(self.read_c_string()?);
}
// code_length isn't included in the DefineFunction's action length.
let code_length = self.read_u16()?;
let code_length = usize::from(self.read_u16()?);
*action_length += code_length;
Ok(Action::DefineFunction {
name,
params,
actions: self.read_slice(code_length.into()),
actions: self.read_slice(code_length)?,
})
}
fn read_define_function_2(&mut self) -> Result<Action<'a>> {
fn read_define_function_2(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
let name = self.read_c_string()?;
let num_params = self.read_u16()?;
let num_registers = self.read_u8()?; // Number of registers
@ -312,7 +336,8 @@ impl<'a> Reader<'a> {
});
}
// code_length isn't included in the DefineFunction's length.
let code_length = self.read_u16()?;
let code_length = usize::from(self.read_u16()?);
*action_length += code_length;
Ok(Action::DefineFunction2(Function {
name,
params,
@ -325,23 +350,24 @@ impl<'a> Reader<'a> {
preload_arguments: flags & 0b100 != 0,
suppress_this: flags & 0b10 != 0,
preload_this: flags & 0b1 != 0,
actions: self.read_slice(code_length.into()),
actions: self.read_slice(code_length)?,
}))
}
fn read_try(&mut self) -> Result<Action<'a>> {
fn read_try(&mut self, length: &mut usize) -> Result<Action<'a>> {
let flags = self.read_u8()?;
let try_length = self.read_u16()?;
let catch_length = self.read_u16()?;
let finally_length = self.read_u16()?;
let try_length = usize::from(self.read_u16()?);
let catch_length = usize::from(self.read_u16()?);
let finally_length = usize::from(self.read_u16()?);
*length += try_length + catch_length + finally_length;
let catch_var = if flags & 0b100 != 0 {
CatchVar::Var(self.read_c_string()?)
} else {
CatchVar::Register(self.read_u8()?)
};
let try_actions = self.read_slice(try_length.into());
let catch_actions = self.read_slice(catch_length.into());
let finally_actions = self.read_slice(finally_length.into());
let try_actions = self.read_slice(try_length)?;
let catch_actions = self.read_slice(catch_length)?;
let finally_actions = self.read_slice(finally_length)?;
Ok(Action::Try(TryBlock {
try_actions,
catch: if flags & 0b1 != 0 {

View File

@ -1,6 +1,7 @@
use crate::avm2::types::*;
use crate::error::{Error, Result};
use crate::read::SwfRead;
use std::io::{Error, ErrorKind, Read, Result};
use std::io::Read;
pub struct Reader<R: Read> {
inner: R,
@ -138,7 +139,7 @@ impl<R: Read> Reader<R> {
0x18 => Namespace::Protected(name),
0x19 => Namespace::Explicit(name),
0x1a => Namespace::StaticProtected(name),
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid namespace kind")),
_ => return Err(Error::invalid_data("Invalid namespace kind")),
})
}
@ -184,7 +185,7 @@ impl<R: Read> Reader<R> {
0x1c => Multiname::MultinameLA {
namespace_set: self.read_index()?,
},
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid multiname kind")),
_ => return Err(Error::invalid_data("Invalid multiname kind")),
})
}
@ -314,7 +315,7 @@ impl<R: Read> Reader<R> {
0x18 => DefaultValue::Protected(Index::new(index)),
0x19 => DefaultValue::Explicit(Index::new(index)),
0x1a => DefaultValue::StaticProtected(Index::new(index)),
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid default value")),
_ => return Err(Error::invalid_data("Invalid default value")),
})
}
@ -339,7 +340,7 @@ impl<R: Read> Reader<R> {
0x18 => DefaultValue::Protected(Index::new(index)),
0x19 => DefaultValue::Explicit(Index::new(index)),
0x1a => DefaultValue::StaticProtected(Index::new(index)),
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid default value")),
_ => return Err(Error::invalid_data("Invalid default value")),
}))
}
}
@ -455,7 +456,7 @@ impl<R: Read> Reader<R> {
type_name: self.read_index()?,
value: self.read_optional_value()?,
},
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid trait kind")),
_ => return Err(Error::invalid_data("Invalid trait kind")),
};
let mut metadata = vec![];
@ -522,7 +523,7 @@ impl<R: Read> Reader<R> {
let opcode = match OpCode::from_u8(self.read_u8()?) {
Some(o) => o,
None => return Err(Error::new(ErrorKind::InvalidData, "Invalid opcode")),
None => return Err(Error::invalid_data("Invalid opcode")),
};
let op = match opcode {

129
swf/src/error.rs Normal file
View File

@ -0,0 +1,129 @@
use std::{borrow, error, fmt, io};
/// A `Result` from reading SWF data.
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
/// An error occurred while parsing an AVM1 action.
/// This can contain sub-errors with further information (`Error::source`)
Avm1ParseError {
opcode: u8,
source: Option<Box<dyn error::Error + 'static>>,
},
/// Invalid or unknown data was encountered.
InvalidData(borrow::Cow<'static, str>),
/// An error occurred while parsing an SWF tag.
/// This can contain sub-errors with further information (`Error::source`)
SwfParseError {
tag_code: u16,
source: Option<Box<dyn error::Error + 'static>>,
},
/// An IO error occurred (probably unexpected EOF).
IoError(io::Error),
/// This SWF requires unsupported features.
Unsupported(borrow::Cow<'static, str>),
}
impl Error {
/// Helper method to create `Error::Avm1ParseError`.
#[inline]
pub fn avm1_parse_error(opcode: u8) -> Self {
Error::Avm1ParseError {
opcode,
source: None,
}
}
/// Helper method to create `Error::Avm1ParseError`.
#[inline]
pub fn avm1_parse_error_with_source(opcode: u8, source: impl error::Error + 'static) -> Self {
Error::Avm1ParseError {
opcode,
source: Some(Box::new(source)),
}
}
/// Helper method to create `Error::InvalidData`.
#[inline]
pub fn invalid_data(message: impl Into<borrow::Cow<'static, str>>) -> Self {
Error::InvalidData(message.into())
}
/// Helper method to create `Error::SwfParseError`.
#[inline]
pub fn swf_parse_error(tag_code: u16) -> Self {
Error::SwfParseError {
tag_code,
source: None,
}
}
/// Helper method to create `Error::SwfParseError`.
#[inline]
pub fn swf_parse_error_with_source(tag_code: u16, source: impl error::Error + 'static) -> Self {
Error::SwfParseError {
tag_code,
source: Some(Box::new(source)),
}
}
/// Helper method to create `Error::Unsupported`.
#[inline]
pub fn unsupported(message: impl Into<borrow::Cow<'static, str>>) -> Self {
Error::Unsupported(message.into())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::num_traits::FromPrimitive;
match self {
Error::Avm1ParseError { opcode, source } => {
let op = crate::avm1::opcode::OpCode::from_u8(*opcode);
"Error parsing AVM1 action ".fmt(f)?;
if let Some(op) = op {
write!(f, "{:?}", op)?;
} else {
write!(f, "Unknown({})", opcode)?;
};
if let Some(source) = source {
write!(f, ": {}", source)?;
}
Ok(())
}
Error::SwfParseError { tag_code, source } => {
let tag = crate::tag_code::TagCode::from_u16(*tag_code);
"Error parsing SWF tag ".fmt(f)?;
if let Some(tag) = tag {
write!(f, "{:?}", tag)?;
} else {
write!(f, "Unknown({})", tag_code)?;
};
if let Some(source) = source {
write!(f, ": {}", source)?;
}
Ok(())
}
Error::IoError(e) => e.fmt(f),
Error::InvalidData(message) => write!(f, "Invalid data: {}", message),
Error::Unsupported(message) => write!(f, "Unsupported data: {}", message),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use std::ops::Deref;
match self {
Error::Avm1ParseError { source, .. } => source.as_ref().map(|s| s.deref()),
Error::IoError(e) => e.source(),
Error::InvalidData(_) => None,
Error::SwfParseError { source, .. } => source.as_ref().map(|s| s.deref()),
Error::Unsupported(_) => None,
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Error {
Error::IoError(error)
}
}

View File

@ -20,6 +20,7 @@ extern crate xz2;
pub mod avm1;
pub mod avm2;
pub mod error;
pub mod read;
mod tag_code;
mod types;

View File

@ -4,10 +4,11 @@
clippy::unreadable_literal
)]
use crate::error::{Error, Result};
use crate::types::*;
use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashSet;
use std::io::{Error, ErrorKind, Read, Result};
use std::io::{self, Read};
/// Convenience method to parse an SWF.
///
@ -91,8 +92,7 @@ fn make_zlib_reader<'a, R: Read + 'a>(input: R) -> Result<Box<dyn Read + 'a>> {
#[cfg(not(any(feature = "flate2", feature = "libflate")))]
fn make_zlib_reader<'a, R: Read + 'a>(_input: R) -> Result<Box<dyn Read + 'a>> {
Err(Error::new(
ErrorKind::InvalidData,
Err(Error::unsupported(
"Support for Zlib compressed SWFs is not enabled.",
))
}
@ -117,56 +117,55 @@ fn make_lzma_reader<'a, R: Read + 'a>(mut input: R) -> Result<Box<dyn Read + 'a>
#[cfg(not(feature = "lzma-support"))]
fn make_lzma_reader<'a, R: Read + 'a>(_input: R) -> Result<Box<dyn Read + 'a>> {
Err(Error::new(
ErrorKind::InvalidData,
"Support for LZMA compressed SWFs is not enabled.",
Err(Error::unsupported(
"Support for Zlib compressed SWFs is not enabled.",
))
}
pub trait SwfRead<R: Read> {
fn get_inner(&mut self) -> &mut R;
fn read_u8(&mut self) -> Result<u8> {
fn read_u8(&mut self) -> io::Result<u8> {
self.get_inner().read_u8()
}
fn read_u16(&mut self) -> Result<u16> {
fn read_u16(&mut self) -> io::Result<u16> {
self.get_inner().read_u16::<LittleEndian>()
}
fn read_u32(&mut self) -> Result<u32> {
fn read_u32(&mut self) -> io::Result<u32> {
self.get_inner().read_u32::<LittleEndian>()
}
fn read_u64(&mut self) -> Result<u64> {
fn read_u64(&mut self) -> io::Result<u64> {
self.get_inner().read_u64::<LittleEndian>()
}
fn read_i8(&mut self) -> Result<i8> {
fn read_i8(&mut self) -> io::Result<i8> {
self.get_inner().read_i8()
}
fn read_i16(&mut self) -> Result<i16> {
fn read_i16(&mut self) -> io::Result<i16> {
self.get_inner().read_i16::<LittleEndian>()
}
fn read_i32(&mut self) -> Result<i32> {
fn read_i32(&mut self) -> io::Result<i32> {
self.get_inner().read_i32::<LittleEndian>()
}
fn read_fixed8(&mut self) -> Result<f32> {
fn read_fixed8(&mut self) -> io::Result<f32> {
self.read_i16().map(|n| f32::from(n) / 256f32)
}
fn read_fixed16(&mut self) -> Result<f64> {
fn read_fixed16(&mut self) -> io::Result<f64> {
self.read_i32().map(|n| f64::from(n) / 65536f64)
}
fn read_f32(&mut self) -> Result<f32> {
fn read_f32(&mut self) -> io::Result<f32> {
self.get_inner().read_f32::<LittleEndian>()
}
fn read_f64(&mut self) -> Result<f64> {
fn read_f64(&mut self) -> io::Result<f64> {
// Flash weirdly stores f64 as two LE 32-bit chunks.
// First word is the hi-word, second word is the lo-word.
let mut num = [0u8; 8];
@ -189,8 +188,7 @@ pub trait SwfRead<R: Read> {
}
// TODO: There is probably a better way to do this.
// TODO: Verify ANSI for SWF 5 and earlier.
String::from_utf8(bytes)
.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid string data"))
String::from_utf8(bytes).map_err(|_| Error::invalid_data("Invalid string data"))
}
}
@ -210,42 +208,42 @@ impl<R: Read> SwfRead<R> for Reader<R> {
&mut self.input
}
fn read_u8(&mut self) -> Result<u8> {
fn read_u8(&mut self) -> io::Result<u8> {
self.byte_align();
self.input.read_u8()
}
fn read_u16(&mut self) -> Result<u16> {
fn read_u16(&mut self) -> io::Result<u16> {
self.byte_align();
self.input.read_u16::<LittleEndian>()
}
fn read_u32(&mut self) -> Result<u32> {
fn read_u32(&mut self) -> io::Result<u32> {
self.byte_align();
self.input.read_u32::<LittleEndian>()
}
fn read_i8(&mut self) -> Result<i8> {
fn read_i8(&mut self) -> io::Result<i8> {
self.byte_align();
self.input.read_i8()
}
fn read_i16(&mut self) -> Result<i16> {
fn read_i16(&mut self) -> io::Result<i16> {
self.byte_align();
self.input.read_i16::<LittleEndian>()
}
fn read_i32(&mut self) -> Result<i32> {
fn read_i32(&mut self) -> io::Result<i32> {
self.byte_align();
self.input.read_i32::<LittleEndian>()
}
fn read_f32(&mut self) -> Result<f32> {
fn read_f32(&mut self) -> io::Result<f32> {
self.byte_align();
self.input.read_f32::<LittleEndian>()
}
fn read_f64(&mut self) -> Result<f64> {
fn read_f64(&mut self) -> io::Result<f64> {
self.byte_align();
self.input.read_f64::<LittleEndian>()
}
@ -286,7 +284,16 @@ impl<R: Read> Reader<R> {
/// ```
pub fn read_tag(&mut self) -> Result<Tag> {
let (tag_code, length) = self.read_tag_code_and_length()?;
let tag = self.read_tag_with_code(tag_code, length);
if let Err(e) = tag {
return Err(Error::swf_parse_error_with_source(tag_code, e));
}
tag
}
fn read_tag_with_code(&mut self, tag_code: u16, length: usize) -> Result<Tag> {
let mut tag_reader = Reader::new(self.input.by_ref().take(length as u64), self.version);
use crate::tag_code::TagCode;
let tag = match TagCode::from_u16(tag_code) {
@ -634,7 +641,7 @@ impl<R: Read> Reader<R> {
b"FWS" => Compression::None,
b"CWS" => Compression::Zlib,
b"ZWS" => Compression::Lzma,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid SWF")),
_ => return Err(Error::invalid_data("Invalid SWF")),
};
Ok(compression)
}
@ -703,7 +710,8 @@ impl<R: Read> Reader<R> {
}
pub fn read_character_id(&mut self) -> Result<CharacterId> {
self.read_u16()
let id = self.read_u16()?;
Ok(id)
}
pub fn read_rgb(&mut self) -> Result<Color> {
@ -809,7 +817,7 @@ impl<R: Read> Reader<R> {
3 => Language::Korean,
4 => Language::SimplifiedChinese,
5 => Language::TraditionalChinese,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid language code.")),
_ => return Err(Error::invalid_data("Invalid language code")),
})
}
@ -821,11 +829,6 @@ impl<R: Read> Reader<R> {
Ok(tag) => tags.push(tag),
Err(err) => {
// We screwed up reading this tag in some way.
// TODO: We could recover more gracefully in some way.
// Simply throw away this tag, but keep going.
if cfg!(debug_assertions) {
panic!("Error reading tag");
}
return Err(err);
}
}
@ -1011,12 +1014,7 @@ impl<R: Read> Reader<R> {
0b00_000 => TextGridFit::None,
0b01_000 => TextGridFit::Pixel,
0b10_000 => TextGridFit::SubPixel,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid text grid fitting",
))
}
_ => return Err(Error::invalid_data("Invalid text grid fitting")),
},
thickness,
sharpness,
@ -1239,12 +1237,7 @@ impl<R: Read> Reader<R> {
0b00_000000 => FontThickness::Thin,
0b01_000000 => FontThickness::Medium,
0b10_000000 => FontThickness::Thick,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid font thickness type.",
))
}
_ => return Err(Error::invalid_data("Invalid font thickness type.")),
};
let mut zones = vec![];
while let Ok(zone) = self.read_font_align_zone() {
@ -1427,7 +1420,7 @@ impl<R: Read> Reader<R> {
0 => LineCapStyle::Round,
1 => LineCapStyle::None,
2 => LineCapStyle::Square,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let join_style_id = self.read_ubits(2)?;
let has_fill = self.read_bit()?;
@ -1440,13 +1433,13 @@ impl<R: Read> Reader<R> {
0 => LineCapStyle::Round,
1 => LineCapStyle::None,
2 => LineCapStyle::Square,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let join_style = match join_style_id {
0 => LineJoinStyle::Round,
1 => LineJoinStyle::Bevel,
2 => LineJoinStyle::Miter(self.read_fixed8()?),
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let (start_color, end_color) = if !has_fill {
(self.read_rgba()?, self.read_rgba()?)
@ -1528,8 +1521,7 @@ impl<R: Read> Reader<R> {
0x13 => {
if self.version < 8 || shape_version < 2 {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Focal gradients are only supported in SWF version 8 \
or higher.",
));
@ -1568,7 +1560,7 @@ impl<R: Read> Reader<R> {
)
}
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid fill style.")),
_ => return Err(Error::invalid_data("Invalid fill style.")),
};
Ok(fill_style)
}
@ -1718,8 +1710,7 @@ impl<R: Read> Reader<R> {
0x13 => {
if self.version < 8 || shape_version < 4 {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Focal gradients are only supported in SWF version 8 \
or higher.",
));
@ -1738,7 +1729,7 @@ impl<R: Read> Reader<R> {
is_repeating: (fill_style_type & 0b01) == 0,
},
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid fill style.")),
_ => return Err(Error::invalid_data("Invalid fill style.")),
};
Ok(fill_style)
}
@ -1761,7 +1752,7 @@ impl<R: Read> Reader<R> {
0 => LineCapStyle::Round,
1 => LineCapStyle::None,
2 => LineCapStyle::Square,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let join_style_id = self.read_ubits(2)?;
let has_fill = self.read_bit()?;
@ -1774,13 +1765,13 @@ impl<R: Read> Reader<R> {
0 => LineCapStyle::Round,
1 => LineCapStyle::None,
2 => LineCapStyle::Square,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let join_style = match join_style_id {
0 => LineJoinStyle::Round,
1 => LineJoinStyle::Bevel,
2 => LineJoinStyle::Miter(self.read_fixed8()?),
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let color = if !has_fill {
self.read_rgba()?
@ -1819,22 +1810,12 @@ impl<R: Read> Reader<R> {
0 => GradientSpread::Pad,
1 => GradientSpread::Reflect,
2 => GradientSpread::Repeat,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid gradient spread mode.",
))
}
_ => return Err(Error::invalid_data("Invalid gradient spread mode.")),
};
let interpolation = match self.read_ubits(2)? {
0 => GradientInterpolation::RGB,
1 => GradientInterpolation::LinearRGB,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid gradient interpolation mode.",
))
}
_ => return Err(Error::invalid_data("Invalid gradient interpolation mode.")),
};
let num_records = self.read_ubits(4)? as usize;
let mut records = Vec::with_capacity(num_records);
@ -1990,12 +1971,7 @@ impl<R: Read> Reader<R> {
0b01 => PlaceObjectAction::Modify,
0b10 => PlaceObjectAction::Place(self.read_u16()?),
0b11 => PlaceObjectAction::Replace(self.read_u16()?),
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid PlaceObject type",
))
}
_ => return Err(Error::invalid_data("Invalid PlaceObject type")),
};
let matrix = if (flags & 0b100) != 0 {
Some(self.read_matrix()?)
@ -2107,7 +2083,7 @@ impl<R: Read> Reader<R> {
12 => BlendMode::Erase,
13 => BlendMode::Overlay,
14 => BlendMode::HardLight,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid blend mode")),
_ => return Err(Error::invalid_data("Invalid blend mode")),
})
}
@ -2345,7 +2321,7 @@ impl<R: Read> Reader<R> {
num_passes: self.read_ubits(4)? as u8 & 0b011111,
}))
}
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid filter type")),
_ => return Err(Error::invalid_data("Invalid filter type")),
};
self.byte_align();
Ok(filter)
@ -2362,7 +2338,7 @@ impl<R: Read> Reader<R> {
5 => AudioCompression::Nellymoser8Khz,
6 => AudioCompression::Nellymoser,
11 => AudioCompression::Speex,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid audio format.")),
_ => return Err(Error::invalid_data("Invalid audio format.")),
};
let sample_rate = match (flags & 0b11_00) >> 2 {
0 => 5512,
@ -2553,12 +2529,7 @@ impl<R: Read> Reader<R> {
1 => TextAlign::Right,
2 => TextAlign::Center,
3 => TextAlign::Justify,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid edit text alignment",
))
}
_ => return Err(Error::invalid_data("Invalid edit text alignment")),
},
left_margin: Twips::new(self.read_u16()?),
right_margin: Twips::new(self.read_u16()?),
@ -2610,7 +2581,7 @@ impl<R: Read> Reader<R> {
3 => VideoCodec::ScreenVideo,
4 => VideoCodec::VP6,
5 => VideoCodec::VP6WithAlpha,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid video codec.")),
_ => return Err(Error::invalid_data("Invalid video codec.")),
};
Ok(Tag::DefineVideoStream(DefineVideoStream {
id,
@ -2626,12 +2597,7 @@ impl<R: Read> Reader<R> {
0b011_0 => VideoDeblocking::Level2,
0b100_0 => VideoDeblocking::Level3,
0b101_0 => VideoDeblocking::Level4,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid video deblocking value.",
))
}
_ => return Err(Error::invalid_data("Invalid video deblocking value.")),
},
}))
}
@ -2676,7 +2642,7 @@ impl<R: Read> Reader<R> {
3 => BitmapFormat::ColorMap8,
4 if version == 1 => BitmapFormat::Rgb15,
5 => BitmapFormat::Rgb32,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid bitmap format.")),
_ => return Err(Error::invalid_data("Invalid bitmap format.")),
};
let width = self.read_u16()?;
let height = self.read_u16()?;
@ -3142,7 +3108,10 @@ pub mod tests {
fn read_tags() {
for (swf_version, expected_tag, tag_bytes) in test_data::tag_tests() {
let mut reader = Reader::new(&tag_bytes[..], swf_version);
let parsed_tag = reader.read_tag().unwrap();
let parsed_tag = match reader.read_tag() {
Ok(tag) => tag,
Err(e) => panic!("Error parsing tag: {}", e),
};
if parsed_tag != expected_tag {
// Failed, result doesn't match.
panic!(

View File

@ -5,12 +5,13 @@
clippy::unreadable_literal
)]
use crate::error::{Error, Result};
use crate::tag_code::TagCode;
use crate::types::*;
use byteorder::{LittleEndian, WriteBytesExt};
use std::cmp::max;
use std::collections::HashSet;
use std::io::{Error, ErrorKind, Result, Write};
use std::io::{self, Write};
/// Writes an SWF file to an output stream.
/// # Example
@ -98,8 +99,7 @@ fn write_zlib_swf<W: Write>(mut output: W, swf_body: &[u8]) -> Result<()> {
#[cfg(not(any(feature = "flate2", feature = "libflate")))]
fn write_zlib_swf<W: Write>(_output: W, _swf_body: &[u8]) -> Result<()> {
Err(Error::new(
ErrorKind::InvalidData,
Err(Error::unsupported(
"Support for Zlib compressed SWFs is not enabled.",
))
}
@ -121,8 +121,7 @@ fn write_lzma_swf<W: Write>(mut output: W, swf_body: &[u8]) -> Result<()> {
#[cfg(not(feature = "lzma-support"))]
fn write_lzma_swf<W: Write>(_output: W, _swf_body: &[u8]) -> Result<()> {
Err(Error::new(
ErrorKind::InvalidData,
Err(Error::unsupported(
"Support for LZMA compressed SWFs is not enabled.",
))
}
@ -130,47 +129,47 @@ fn write_lzma_swf<W: Write>(_output: W, _swf_body: &[u8]) -> Result<()> {
pub trait SwfWrite<W: Write> {
fn get_inner(&mut self) -> &mut W;
fn write_u8(&mut self, n: u8) -> Result<()> {
fn write_u8(&mut self, n: u8) -> io::Result<()> {
self.get_inner().write_u8(n)
}
fn write_u16(&mut self, n: u16) -> Result<()> {
fn write_u16(&mut self, n: u16) -> io::Result<()> {
self.get_inner().write_u16::<LittleEndian>(n)
}
fn write_u32(&mut self, n: u32) -> Result<()> {
fn write_u32(&mut self, n: u32) -> io::Result<()> {
self.get_inner().write_u32::<LittleEndian>(n)
}
fn write_u64(&mut self, n: u64) -> Result<()> {
fn write_u64(&mut self, n: u64) -> io::Result<()> {
self.get_inner().write_u64::<LittleEndian>(n)
}
fn write_i8(&mut self, n: i8) -> Result<()> {
fn write_i8(&mut self, n: i8) -> io::Result<()> {
self.get_inner().write_i8(n)
}
fn write_i16(&mut self, n: i16) -> Result<()> {
fn write_i16(&mut self, n: i16) -> io::Result<()> {
self.get_inner().write_i16::<LittleEndian>(n)
}
fn write_i32(&mut self, n: i32) -> Result<()> {
fn write_i32(&mut self, n: i32) -> io::Result<()> {
self.get_inner().write_i32::<LittleEndian>(n)
}
fn write_fixed8(&mut self, n: f32) -> Result<()> {
fn write_fixed8(&mut self, n: f32) -> io::Result<()> {
self.write_i16((n * 256f32) as i16)
}
fn write_fixed16(&mut self, n: f64) -> Result<()> {
fn write_fixed16(&mut self, n: f64) -> io::Result<()> {
self.write_i32((n * 65536f64) as i32)
}
fn write_f32(&mut self, n: f32) -> Result<()> {
fn write_f32(&mut self, n: f32) -> io::Result<()> {
self.get_inner().write_f32::<LittleEndian>(n)
}
fn write_f64(&mut self, n: f64) -> Result<()> {
fn write_f64(&mut self, n: f64) -> io::Result<()> {
// Flash weirdly stores f64 as two LE 32-bit chunks.
// First word is the hi-word, second word is the lo-word.
let mut num = [0u8; 8];
@ -182,7 +181,7 @@ pub trait SwfWrite<W: Write> {
self.get_inner().write_all(&num)
}
fn write_c_string(&mut self, s: &str) -> Result<()> {
fn write_c_string(&mut self, s: &str) -> io::Result<()> {
self.get_inner().write_all(s.as_bytes())?;
self.write_u8(0)
}
@ -202,47 +201,47 @@ impl<W: Write> SwfWrite<W> for Writer<W> {
&mut self.output
}
fn write_u8(&mut self, n: u8) -> Result<()> {
fn write_u8(&mut self, n: u8) -> io::Result<()> {
self.flush_bits()?;
self.output.write_u8(n)
}
fn write_u16(&mut self, n: u16) -> Result<()> {
fn write_u16(&mut self, n: u16) -> io::Result<()> {
self.flush_bits()?;
self.output.write_u16::<LittleEndian>(n)
}
fn write_u32(&mut self, n: u32) -> Result<()> {
fn write_u32(&mut self, n: u32) -> io::Result<()> {
self.flush_bits()?;
self.output.write_u32::<LittleEndian>(n)
}
fn write_i8(&mut self, n: i8) -> Result<()> {
fn write_i8(&mut self, n: i8) -> io::Result<()> {
self.flush_bits()?;
self.output.write_i8(n)
}
fn write_i16(&mut self, n: i16) -> Result<()> {
fn write_i16(&mut self, n: i16) -> io::Result<()> {
self.flush_bits()?;
self.output.write_i16::<LittleEndian>(n)
}
fn write_i32(&mut self, n: i32) -> Result<()> {
fn write_i32(&mut self, n: i32) -> io::Result<()> {
self.flush_bits()?;
self.output.write_i32::<LittleEndian>(n)
}
fn write_f32(&mut self, n: f32) -> Result<()> {
fn write_f32(&mut self, n: f32) -> io::Result<()> {
self.flush_bits()?;
self.output.write_f32::<LittleEndian>(n)
}
fn write_f64(&mut self, n: f64) -> Result<()> {
fn write_f64(&mut self, n: f64) -> io::Result<()> {
self.flush_bits()?;
self.output.write_f64::<LittleEndian>(n)
}
fn write_c_string(&mut self, s: &str) -> Result<()> {
fn write_c_string(&mut self, s: &str) -> io::Result<()> {
self.flush_bits()?;
self.get_inner().write_all(s.as_bytes())?;
self.write_u8(0)
@ -277,7 +276,7 @@ impl<W: Write> Writer<W> {
Ok(())
}
fn flush_bits(&mut self) -> Result<()> {
fn flush_bits(&mut self) -> io::Result<()> {
if self.bit_index != 8 {
self.output.write_u8(self.byte)?;
self.bit_index = 8;
@ -341,7 +340,8 @@ impl<W: Write> Writer<W> {
}
fn write_character_id(&mut self, id: CharacterId) -> Result<()> {
self.write_u16(id)
self.write_u16(id)?;
Ok(())
}
fn write_rgb(&mut self, color: &Color) -> Result<()> {
@ -509,7 +509,8 @@ impl<W: Write> Writer<W> {
Language::Korean => 3,
Language::SimplifiedChinese => 4,
Language::TraditionalChinese => 5,
})
})?;
Ok(())
}
fn write_tag(&mut self, tag: &Tag) -> Result<()> {
@ -918,12 +919,7 @@ impl<W: Write> Writer<W> {
2 => self.write_place_object_2_or_3(place_object, 2)?,
3 => self.write_place_object_2_or_3(place_object, 3)?,
4 => self.write_place_object_2_or_3(place_object, 4)?,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid PlaceObject version.",
))
}
_ => return Err(Error::invalid_data("Invalid PlaceObject version.")),
},
Tag::RemoveObject(ref remove_object) => {
@ -1191,8 +1187,7 @@ impl<W: Write> Writer<W> {
if data.start.fill_styles.len() != data.end.fill_styles.len()
|| data.start.line_styles.len() != data.end.line_styles.len()
{
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Start and end state of a morph shape must have the same number of styles.",
));
}
@ -1337,8 +1332,7 @@ impl<W: Write> Writer<W> {
},
) => {
if self.version < 8 || shape_version < 2 {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Focal gradients are only support in SWF version 8 \
and higher.",
));
@ -1379,8 +1373,7 @@ impl<W: Write> Writer<W> {
}
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Morph start and end fill styles must be the same variant.",
))
}
@ -1392,8 +1385,7 @@ impl<W: Write> Writer<W> {
self.write_matrix(&start.matrix)?;
self.write_matrix(&end.matrix)?;
if start.records.len() != end.records.len() {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Morph start and end gradient must have the same amount of records.",
));
}
@ -1428,8 +1420,7 @@ impl<W: Write> Writer<W> {
|| start.allow_close != end.allow_close
|| start.end_cap != end.end_cap
{
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Morph start and end line styles must have the same join parameters.",
));
}
@ -1483,8 +1474,7 @@ impl<W: Write> Writer<W> {
}
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Morph start and end line styles must both have fill styles.",
))
}
@ -1553,12 +1543,7 @@ impl<W: Write> Writer<W> {
2 => TagCode::DefineShape2,
3 => TagCode::DefineShape3,
4 => TagCode::DefineShape4,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid DefineShape version.",
))
}
_ => return Err(Error::invalid_data("Invalid DefineShape version.")),
};
self.write_tag_header(tag_code, buf.len() as u32)?;
self.output.write_all(&buf)?;
@ -1649,7 +1634,8 @@ impl<W: Write> Writer<W> {
BlendMode::Erase => 12,
BlendMode::Overlay => 13,
BlendMode::HardLight => 14,
})
})?;
Ok(())
}
fn write_shape_styles(&mut self, styles: &ShapeStyles, shape_version: u8) -> Result<()> {
@ -1752,8 +1738,7 @@ impl<W: Write> Writer<W> {
}
if let Some(ref new_styles) = style_change.new_styles {
if shape_version < 2 {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Only DefineShape2 and higher may change styles.",
));
}
@ -1790,8 +1775,7 @@ impl<W: Write> Writer<W> {
focal_point,
} => {
if self.version < 8 {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"Focal gradients are only support in SWF version 8 \
and higher.",
));
@ -1911,8 +1895,7 @@ impl<W: Write> Writer<W> {
if let PlaceObjectAction::Place(character_id) = place_object.action {
writer.write_u16(character_id)?;
} else {
return Err(Error::new(
ErrorKind::InvalidData,
return Err(Error::invalid_data(
"PlaceObject version 1 can only use a Place action.",
));
}
@ -2074,12 +2057,7 @@ impl<W: Write> Writer<W> {
2 => TagCode::PlaceObject2,
3 => TagCode::PlaceObject3,
4 => TagCode::PlaceObject4,
_ => {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid PlaceObject version.",
))
}
_ => return Err(Error::invalid_data("Invalid PlaceObject version.")),
};
self.write_tag_header(tag_code, buf.len() as u32)?;
self.output.write_all(&buf)?;
@ -2310,7 +2288,7 @@ impl<W: Write> Writer<W> {
11025 => 1,
22050 => 2,
44100 => 3,
_ => return Err(Error::new(ErrorKind::InvalidData, "Invalid sample rate.")),
_ => return Err(Error::invalid_data("Invalid sample rate.")),
},
)?;
self.write_bit(sound_format.is_16_bit)?;
@ -2447,14 +2425,19 @@ impl<W: Write> Writer<W> {
writer.write_u16(layout.descent)?;
writer.write_i16(layout.leading)?;
for glyph in &font.glyphs {
writer.write_i16(glyph.advance.ok_or_else(|| {
Error::new(ErrorKind::InvalidData, "glyph.advance cannot be None")
})?)?;
writer.write_i16(
glyph
.advance
.ok_or_else(|| Error::invalid_data("glyph.advance cannot be None"))?,
)?;
}
for glyph in &font.glyphs {
writer.write_rectangle(glyph.bounds.as_ref().ok_or_else(|| {
Error::new(ErrorKind::InvalidData, "glyph.bounds cannot be None")
})?)?;
writer.write_rectangle(
glyph
.bounds
.as_ref()
.ok_or_else(|| Error::invalid_data("glyph.bounds cannot be None"))?,
)?;
}
writer.write_u16(layout.kerning.len() as u16)?;
for kerning_record in &layout.kerning {
@ -2688,7 +2671,8 @@ impl<W: Write> Writer<W> {
}
fn write_debug_id(&mut self, debug_id: &DebugId) -> Result<()> {
self.get_inner().write_all(debug_id)
self.get_inner().write_all(debug_id)?;
Ok(())
}
fn write_tag_header(&mut self, tag_code: TagCode, length: u32) -> Result<()> {
@ -2700,12 +2684,13 @@ impl<W: Write> Writer<W> {
let mut tag_code_and_length: u16 = tag_code << 6;
if length < 0b111111 {
tag_code_and_length |= length as u16;
self.write_u16(tag_code_and_length)
self.write_u16(tag_code_and_length)?;
} else {
tag_code_and_length |= 0b111111;
self.write_u16(tag_code_and_length)?;
self.write_u32(length)
self.write_u32(length)?;
}
Ok(())
}
fn write_tag_list(&mut self, tags: &[Tag]) -> Result<()> {
@ -2762,7 +2747,6 @@ mod tests {
use super::Writer;
use super::*;
use crate::test_data;
use std::io::Result;
fn new_swf() -> Swf {
Swf {
@ -2788,7 +2772,8 @@ mod tests {
let mut buf = Vec::new();
let mut swf = new_swf();
swf.header.compression = compression;
write_swf(&swf, &mut buf)
write_swf(&swf, &mut buf)?;
Ok(())
}
assert!(
write_dummy_swf(Compression::None).is_ok(),