Skip to content

Commit

Permalink
Merge branch 'feature/parse_opt_records' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-weis committed Dec 19, 2024
2 parents 8769b09 + d2ea066 commit b6bb9b2
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>,
lcid: u32,
lcid_invoke: u32,
/// Specifies the code page for the VBA project.
Expand All @@ -181,7 +182,7 @@ pub struct Information {
lib_flags: u32,
version_major: u32,
version_minor: u16,
constants: String,
constants: Option<String>,
}

/// Specifies the containing module's type.
Expand Down
46 changes: 36 additions & 10 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ fn parse_syskind(i: &[u8]) -> IResult<&[u8], SysKind, FormatError<&[u8]>> {
}
}

fn parse_compat(i: &[u8]) -> IResult<&[u8], Option<u32>, 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)?;
Expand Down Expand Up @@ -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<u8>, FormatError<&[u8]>> {
fn parse_constants(i: &[u8]) -> IResult<&[u8], Option<Vec<u8>>, 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<u8>, FormatError<&[u8]>> {
fn parse_constants_unicode(i: &[u8]) -> IResult<&[u8], Option<Vec<u8>>, 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))
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -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)?;
Expand All @@ -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)?;

Expand All @@ -604,6 +629,7 @@ pub(crate) fn parse_project_information(
ProjectInformation {
information: Information {
sys_kind,
compat,
lcid,
lcid_invoke,
code_page,
Expand Down
91 changes: 90 additions & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::parser::decompress;
use super::parser::{decompress, parse_project_information};

#[test]
fn copy_token_decoder() {
Expand Down Expand Up @@ -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());
}

0 comments on commit b6bb9b2

Please sign in to comment.