diff --git a/src/lib.rs b/src/lib.rs index e61c341..5dda5a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,7 @@ pub enum Reference { pub struct Information { /// Specifies the platform for which the VBA project is created. pub sys_kind: SysKind, + compat: Option, lcid: u32, lcid_invoke: u32, /// Specifies the code page for the VBA project. @@ -181,7 +182,7 @@ pub struct Information { lib_flags: u32, version_major: u32, version_minor: u16, - constants: String, + constants: Option, } /// Specifies the containing module's type. diff --git a/src/parser.rs b/src/parser.rs index 7bb5b35..8a8a44f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -164,6 +164,15 @@ fn parse_syskind(i: &[u8]) -> IResult<&[u8], SysKind, FormatError<&[u8]>> { } } +fn parse_compat(i: &[u8]) -> IResult<&[u8], Option, FormatError<&[u8]>> { + const COMPAT_SIGNATURE: &[u8] = &[0x4A, 0x00]; + let (i, compat) = opt(preceded( + tuple((tag(COMPAT_SIGNATURE), tag(U32_FIXED_SIZE_4))), + le_u32, + ))(i)?; + Ok((i, compat)) +} + fn parse_lcid(i: &[u8]) -> IResult<&[u8], u32, FormatError<&[u8]>> { const LCID_SIGNATURE: &[u8] = &[0x02, 0x00]; let (i, lcid) = preceded(tuple((tag(LCID_SIGNATURE), tag(U32_FIXED_SIZE_4))), le_u32)(i)?; @@ -252,17 +261,21 @@ fn parse_version(i: &[u8]) -> IResult<&[u8], (u32, u16), FormatError<&[u8]>> { Ok((i, version)) } -fn parse_constants(i: &[u8]) -> IResult<&[u8], Vec, FormatError<&[u8]>> { +fn parse_constants(i: &[u8]) -> IResult<&[u8], Option>, FormatError<&[u8]>> { const CONSTANTS_SIGNATURE: &[u8] = &[0x0c, 0x00]; - let (i, constants) = preceded(tag(CONSTANTS_SIGNATURE), length_data(le_u32))(i)?; - Ok((i, constants.to_vec())) + let (i, constants) = opt(preceded(tag(CONSTANTS_SIGNATURE), length_data(le_u32)))(i)?; + let constants = constants.map(|slice| slice.to_vec()); + Ok((i, constants)) } -fn parse_constants_unicode(i: &[u8]) -> IResult<&[u8], Vec, FormatError<&[u8]>> { +fn parse_constants_unicode(i: &[u8]) -> IResult<&[u8], Option>, FormatError<&[u8]>> { const CONSTANTS_UNICODE_SIGNATURE: &[u8] = &[0x3c, 0x00]; - let (i, constants_unicode) = - preceded(tag(CONSTANTS_UNICODE_SIGNATURE), length_data(le_u32))(i)?; - Ok((i, constants_unicode.to_vec())) + let (i, constants_unicode) = opt(preceded( + tag(CONSTANTS_UNICODE_SIGNATURE), + length_data(le_u32), + ))(i)?; + let constants_unicode = constants_unicode.map(|slice| slice.to_vec()); + Ok((i, constants_unicode)) } // ------------------------------------------------------------------------- @@ -558,6 +571,7 @@ pub(crate) fn parse_project_information( i: &[u8], ) -> IResult<&[u8], ProjectInformation, FormatError<&[u8]>> { let (i, sys_kind) = parse_syskind(i)?; + let (i, compat) = parse_compat(i)?; let (i, lcid) = parse_lcid(i)?; let (i, lcid_invoke) = parse_lcid_invoke(i)?; let (i, code_page) = parse_code_page(i)?; @@ -581,11 +595,22 @@ pub(crate) fn parse_project_information( let (i, lib_flags) = parse_lib_flags(i)?; let (i, (version_major, version_minor)) = parse_version(i)?; + // The `PROJECTCONSTANTS` record is optional (as a whole); make sure to only parse the + // Unicode portion if `parse_constants` returned `Some`. + // + // TODO: Consider consolidating CP and Unicode parsing into a single function. This + // would avoid having to subsequently deal with the outcome of this function. let (i, constants) = parse_constants(i)?; - let constants = cp_to_string(&constants, code_page); + let constants = constants.map(|constants| cp_to_string(&constants, code_page)); - // constants_unicode MUST contain the UTF-16 encoding of constants. Can safely be dropped. - let (i, _constants_unicode) = parse_constants_unicode(i)?; + let i = if constants.is_some() { + // constants_unicode MUST contain the UTF-16 encoding of constants. Can safely be + // dropped. + let (i, _constants_unicode) = parse_constants_unicode(i)?; + i + } else { + i + }; let (i, references) = parse_references(i, code_page)?; @@ -604,6 +629,7 @@ pub(crate) fn parse_project_information( ProjectInformation { information: Information { sys_kind, + compat, lcid, lcid_invoke, code_page, diff --git a/src/tests.rs b/src/tests.rs index b875ef0..0b6ea2d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,4 @@ -use super::parser::decompress; +use super::parser::{decompress, parse_project_information}; #[test] fn copy_token_decoder() { @@ -38,3 +38,92 @@ fn copy_token_decoder() { let contents = decompress(CONTAINER_3).unwrap().1; assert_eq!(contents, CONTENTS_3); } + +#[test] +fn proj_info_opt_records() { + // Version 11 of the `[MS-OVBA]` specification introduced an optional + // `PROJECTCOMPATVERSION` record following the `PROJECTSYSKIND` record. This test + // verifies that this addition is properly handled by the parser. + // + // In addition, this test verifies that the final `PROJECTCONSTANTS` is treated as + // optional (which it should have been all along). + // + // The four test inputs represent the `2x2` matrix of combinations of optional + // records. + + const INPUT_NONE_NONE: &[u8] = b"\x01\x00\x04\x00\x00\x00\x02\x00\x00\x00\ + \x02\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x14\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x03\x00\x02\x00\x00\x00\xE4\x04\ + \x04\x00\x01\x00\x00\x00\x41\ + \x05\x00\x01\x00\x00\x00\x41\x40\x00\x02\x00\x00\x00\x41\x00\ + \x06\x00\x00\x00\x00\x00\x3D\x00\x00\x00\x00\x00\ + \x07\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x08\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x09\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\ + \x0F\x00\x02\x00\x00\x00\x00\x00\ + \x13\x00\x02\x00\x00\x00\xFF\xFF\ + \x10\x00\ + \x00\x00\x00\x00"; + let res = parse_project_information(INPUT_NONE_NONE); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.1.information.constants.is_none()); + + const INPUT_NONE_SOME: &[u8] = b"\x01\x00\x04\x00\x00\x00\x02\x00\x00\x00\ + \x02\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x14\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x03\x00\x02\x00\x00\x00\xE4\x04\ + \x04\x00\x01\x00\x00\x00\x41\ + \x05\x00\x01\x00\x00\x00\x41\x40\x00\x02\x00\x00\x00\x41\x00\ + \x06\x00\x00\x00\x00\x00\x3D\x00\x00\x00\x00\x00\ + \x07\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x08\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x09\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\ + \x0C\x00\x00\x00\x00\x00\x3C\x00\x00\x00\x00\x00\ + \x0F\x00\x02\x00\x00\x00\x00\x00\ + \x13\x00\x02\x00\x00\x00\xFF\xFF\ + \x10\x00\ + \x00\x00\x00\x00"; + let res = parse_project_information(INPUT_NONE_SOME); + assert!(res.is_ok()); + let res = res.unwrap(); + assert!(res.1.information.constants.is_some()); + + const INPUT_SOME_NONE: &[u8] = b"\x01\x00\x04\x00\x00\x00\x02\x00\x00\x00\ + \x4A\x00\x04\x00\x00\x00\x01\x02\x03\x04\ + \x02\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x14\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x03\x00\x02\x00\x00\x00\xE4\x04\ + \x04\x00\x01\x00\x00\x00\x41\ + \x05\x00\x01\x00\x00\x00\x41\x40\x00\x02\x00\x00\x00\x41\x00\ + \x06\x00\x00\x00\x00\x00\x3D\x00\x00\x00\x00\x00\ + \x07\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x08\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x09\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\ + \x0F\x00\x02\x00\x00\x00\x00\x00\ + \x13\x00\x02\x00\x00\x00\xFF\xFF\ + \x10\x00\ + \x00\x00\x00\x00"; + let res = parse_project_information(INPUT_SOME_NONE); + assert!(res.is_ok()); + + const INPUT_SOME_SOME: &[u8] = b"\x01\x00\x04\x00\x00\x00\x02\x00\x00\x00\ + \x4A\x00\x04\x00\x00\x00\x01\x02\x03\x04\ + \x02\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x14\x00\x04\x00\x00\x00\x09\x04\x00\x00\ + \x03\x00\x02\x00\x00\x00\xE4\x04\ + \x04\x00\x01\x00\x00\x00\x41\ + \x05\x00\x01\x00\x00\x00\x41\x40\x00\x02\x00\x00\x00\x41\x00\ + \x06\x00\x00\x00\x00\x00\x3D\x00\x00\x00\x00\x00\ + \x07\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x08\x00\x04\x00\x00\x00\x00\x00\x00\x00\ + \x09\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\ + \x0C\x00\x00\x00\x00\x00\x3C\x00\x00\x00\x00\x00\ + \x0F\x00\x02\x00\x00\x00\x00\x00\ + \x13\x00\x02\x00\x00\x00\xFF\xFF\ + \x10\x00\ + \x00\x00\x00\x00"; + let res = parse_project_information(INPUT_SOME_SOME); + assert!(res.is_ok()); +}