From b47a360cda63299b834278d81ce21d44d915aea8 Mon Sep 17 00:00:00 2001 From: Tim Weis Date: Thu, 19 Dec 2024 13:25:27 +0100 Subject: [PATCH 1/4] testing * Introduces a test to verify that the `PROJECTCONSTANTS` is treated as optional. --- src/tests.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/tests.rs b/src/tests.rs index b875ef0..a6f5f5e 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,51 @@ 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()); + + 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()); +} From 81086b05ceac6e3237c829c4ab2e8dd8e6caf6fe Mon Sep 17 00:00:00 2001 From: Tim Weis Date: Thu, 19 Dec 2024 15:03:47 +0100 Subject: [PATCH 2/4] fix * Fixes the `proj_info_opt_records` test that verifies that the `PROJECTCONSTANTS` is treated as optional. --- src/lib.rs | 2 +- src/parser.rs | 35 +++++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e61c341..a3d0f9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,7 +181,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..ce051d9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -252,17 +252,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)) } // ------------------------------------------------------------------------- @@ -581,11 +585,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)?; From 41290a184c71dd4a646a8de8fe78a8528f65ba73 Mon Sep 17 00:00:00 2001 From: Tim Weis Date: Thu, 19 Dec 2024 15:15:54 +0100 Subject: [PATCH 3/4] testing * Introduces a test to verify that the optional `PROJECTCOMPATVERSION` (introduced in v11) is accepted. --- src/tests.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index a6f5f5e..0b6ea2d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -67,6 +67,8 @@ fn proj_info_opt_records() { \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\ @@ -85,4 +87,43 @@ fn proj_info_opt_records() { \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()); } From d0e3e1aa868e3bfa442203325342292cbeeaed04 Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Thu, 12 Dec 2024 10:18:52 +0100 Subject: [PATCH 4/4] Parser: read optional PROJECTCOMPATVERSION Record. This record is optional and according to the OBVA documentation not written by VBA 5.0. Previously, if this record was found, the parser would simply panic. --- src/lib.rs | 1 + src/parser.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a3d0f9a..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. diff --git a/src/parser.rs b/src/parser.rs index ce051d9..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)?; @@ -562,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)?; @@ -619,6 +629,7 @@ pub(crate) fn parse_project_information( ProjectInformation { information: Information { sys_kind, + compat, lcid, lcid_invoke, code_page,