Skip to content

Commit

Permalink
Add signature file for VBA projects (2nd try) (#415)
Browse files Browse the repository at this point in the history
Add support for signed VBA projects.

#415
  • Loading branch information
HolgiHo authored Sep 20, 2023
1 parent 44e72c5 commit eb76eed
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 5 deletions.
25 changes: 25 additions & 0 deletions docs/src/working_with_macros.dox
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ Python xlsxwriter module:
Utility to extract a vbaProject.bin binary from an
Excel 2007+ xlsm macro file ...

If the VBA project is signed, `vba_extract.py` also extracts the
`vbaProjectSignature.bin` file from the xlsm file. For adding a VBA project
signature see @ww_macros_signature.


@section ww_macros_adding Adding the VBA macros to a libxlsxwriter file

Expand Down Expand Up @@ -143,6 +147,27 @@ clarity:
@note This step is particularly important for macros created with non-English
versions of Excel.


@section ww_macros_signature Adding signed VBA macros to a libxlsxwriter file

VBA macros can be signed in Excel to allow for blocking execution of unsigned
macros in certain environments, for example.

The `vba_extract.py utility` can be used to extract the `vbaProject.bin` and
`vbaProjectSignature.bin` files from an existing xlsm file with signed macros.

To add these files to the libxlsxwriter workbook use the
`workbook_add_signed_vba_project()` function:

@code
workbook_add_signed_vba_project(workbook,
"./vbaProject.bin", "./vbaProjectSignature.bin");
@endcode

@note The names don't have to be `vbaProject.bin` and `vbaProjectSignature.bin`.
Any suitable paths/names for existing VBA project or signature files will do.


@section ww_macros_debugging What to do if it doesn't work

The libxlsxwriter test suite contains several tests to ensure that this feature
Expand Down
32 changes: 31 additions & 1 deletion include/xlsxwriter/workbook.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ typedef struct lxw_workbook {
lxw_hash_table *used_dxf_formats;

char *vba_project;
char *vba_project_signature;
char *vba_codename;

lxw_format *default_url_format;
Expand Down Expand Up @@ -966,7 +967,7 @@ lxw_error workbook_validate_sheet_name(lxw_workbook *workbook,
* workbook_add_vba_project(workbook, "vbaProject.bin");
* @endcode
*
* Only one `vbaProject.bin file` can be added per workbook. The name doesn't
* Only one `vbaProject.bin` file can be added per workbook. The name doesn't
* have to be `vbaProject.bin`. Any suitable path/name for an existing VBA bin
* file will do.
*
Expand All @@ -985,6 +986,35 @@ lxw_error workbook_validate_sheet_name(lxw_workbook *workbook,
lxw_error workbook_add_vba_project(lxw_workbook *workbook,
const char *filename);

/**
* @brief Add a vbaProject binary and a vbaProjectSignature binary to the Excel
* workbook.
*
* @param workbook Pointer to a lxw_workbook instance.
* @param vba_project The path/filename of the vbaProject.bin file.
* @param signature The path/filename of the vbaProjectSignature.bin file.
*
* The `%workbook_add_signed_vba_project()` function can be used to add digitally
* signed macros or functions to a workbook. The function adds a binary VBA project
* file and a binary VBA project signature file that have been extracted from an
* existing Excel xlsm file with digitally signed macros:
*
* @code
* workbook_add_signed_vba_project(workbook, "vbaProject.bin", "vbaProjectSignature.bin");
* @endcode
*
* Only one `vbaProject.bin` file can be added per workbook. The name doesn't
* have to be `vbaProject.bin`. Any suitable path/name for an existing VBA bin
* file will do. The same applies for `vbaProjectSignature.bin`.
*
* See also @ref working_with_macros
*
* @return A #lxw_error.
*/
lxw_error workbook_add_signed_vba_project(lxw_workbook *workbook,
const char *vba_project,
const char *signature);

/**
* @brief Set the VBA name for the workbook.
*
Expand Down
87 changes: 85 additions & 2 deletions src/packager.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* | | |____ theme1.xml
* | |
* | |_____rels
* | |____ workbook.xml.rels
* | |____ workbook.xml.rels
* |
* |_____rels
* |____ .rels
Expand Down Expand Up @@ -81,7 +81,7 @@ STATIC lxw_error _write_vml_drawing_rels_file(lxw_packager *self,
#ifdef _WIN32

/* Silence Windows warning with duplicate symbol for SLIST_ENTRY in local
* queue.h and widows.h. */
* queue.h and windows.h. */
#undef SLIST_ENTRY

#include <windows.h>
Expand Down Expand Up @@ -450,6 +450,35 @@ _add_vba_project(lxw_packager *self)
return LXW_NO_ERROR;
}

/*
* Write the xl/vbaProjectSignature.bin file.
*/
STATIC lxw_error
_add_vba_project_signature(lxw_packager *self)
{
lxw_workbook *workbook = self->workbook;
lxw_error err;
FILE *image_stream;

if (!workbook->vba_project_signature)
return LXW_NO_ERROR;

/* Check that the image file exists and can be opened. */
image_stream = lxw_fopen(workbook->vba_project_signature, "rb");
if (!image_stream) {
LXW_WARN_FORMAT1("Error adding vbaProjectSignature.bin to xlsx file: "
"file doesn't exist or can't be opened: %s.",
workbook->vba_project_signature);
return LXW_ERROR_CREATING_TMPFILE;
}

err = _add_file_to_zip(self, image_stream, "xl/vbaProjectSignature.bin");
fclose(image_stream);
RETURN_ON_ERROR(err);

return LXW_NO_ERROR;
}

/*
* Write the chart files.
*/
Expand Down Expand Up @@ -1244,6 +1273,10 @@ _write_content_types_file(lxw_packager *self)
lxw_ct_add_override(content_types, "/xl/workbook.xml",
LXW_APP_DOCUMENT "spreadsheetml.sheet.main+xml");

if (workbook->vba_project_signature)
lxw_ct_add_override(content_types, "/xl/vbaProjectSignature.bin",
"application/vnd.ms-office.vbaProjectSignature");

STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) {
if (sheet->is_chartsheet) {
lxw_snprintf(filename, LXW_FILENAME_LENGTH,
Expand Down Expand Up @@ -1637,6 +1670,50 @@ _write_vml_drawing_rels_file(lxw_packager *self, lxw_worksheet *worksheet,
return err;
}

/*
* Write the vbaProject .rels xml file.
*/
STATIC lxw_error
_write_vba_project_rels_file(lxw_packager *self)
{
lxw_relationships *rels;
lxw_workbook *workbook = self->workbook;
lxw_error err = LXW_NO_ERROR;
char *buffer = NULL;
size_t buffer_size = 0;

if (!workbook->vba_project_signature)
return LXW_NO_ERROR;

rels = lxw_relationships_new();
if (!rels) {
err = LXW_ERROR_MEMORY_MALLOC_FAILED;
goto mem_error;
}

rels->file = lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir);
if (!rels->file) {
err = LXW_ERROR_CREATING_TMPFILE;
goto mem_error;
}

lxw_add_ms_package_relationship(rels, "/vbaProjectSignature",
"vbaProjectSignature.bin");

lxw_relationships_assemble_xml_file(rels);

err = _add_to_zip(self, rels->file, &buffer, &buffer_size,
"xl/_rels/vbaProject.bin.rels");

fclose(rels->file);
free(buffer);

mem_error:
lxw_free_relationships(rels);

return err;
}

/*
* Write the _rels/.rels xml file.
*/
Expand Down Expand Up @@ -1863,6 +1940,12 @@ lxw_create_package(lxw_packager *self)
error = _add_vba_project(self);
RETURN_AND_ZIPCLOSE_ON_ERROR(error);

error = _add_vba_project_signature(self);
RETURN_AND_ZIPCLOSE_ON_ERROR(error);

error = _write_vba_project_rels_file(self);
RETURN_AND_ZIPCLOSE_ON_ERROR(error);

error = _write_core_file(self);
RETURN_AND_ZIPCLOSE_ON_ERROR(error);

Expand Down
40 changes: 38 additions & 2 deletions src/workbook.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ lxw_workbook_free(lxw_workbook *workbook)
free(workbook->options.tmpdir);
free(workbook->ordered_charts);
free(workbook->vba_project);
free(workbook->vba_project_signature);
free(workbook->vba_codename);
free(workbook);
}
Expand Down Expand Up @@ -2638,15 +2639,15 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename)

if (!filename) {
LXW_WARN("workbook_add_vba_project(): "
"filename must be specified.");
"project filename must be specified.");
return LXW_ERROR_NULL_PARAMETER_IGNORED;
}

/* Check that the vbaProject file exists and can be opened. */
filehandle = lxw_fopen(filename, "rb");
if (!filehandle) {
LXW_WARN_FORMAT1("workbook_add_vba_project(): "
"file doesn't exist or can't be opened: %s.",
"project file doesn't exist or can't be opened: %s.",
filename);
return LXW_ERROR_PARAMETER_VALIDATION;
}
Expand All @@ -2657,6 +2658,41 @@ workbook_add_vba_project(lxw_workbook *self, const char *filename)
return LXW_NO_ERROR;
}

/*
* Add a vbaProject binary and a vbaProjectSignature binary to the Excel workbook.
*/
lxw_error
workbook_add_signed_vba_project(lxw_workbook *self,
const char *vba_project,
const char *signature)
{
FILE *filehandle;

lxw_error error = workbook_add_vba_project(self, vba_project);
if (error != LXW_NO_ERROR)
return error;

if (!signature) {
LXW_WARN("workbook_add_signed_vba_project(): "
"signature filename must be specified.");
return LXW_ERROR_NULL_PARAMETER_IGNORED;
}

/* Check that the vbaProjectSignature file exists and can be opened. */
filehandle = lxw_fopen(signature, "rb");
if (!filehandle) {
LXW_WARN_FORMAT1("workbook_add_signed_vba_project(): "
"signature file doesn't exist or can't be opened: %s.",
signature);
return LXW_ERROR_PARAMETER_VALIDATION;
}
fclose(filehandle);

self->vba_project_signature = lxw_strdup(signature);

return LXW_NO_ERROR;
}

/*
* Set the VBA name for the workbook.
*/
Expand Down
Binary file added test/functional/src/images/vbaProject05.bin
Binary file not shown.
Binary file not shown.
30 changes: 30 additions & 0 deletions test/functional/src/test_macro04.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*****************************************************************************
* Test cases for libxlsxwriter.
*
* Test to compare output against Excel files.
*
* Copyright 2014-2022, John McNamara, [email protected]
*
*/

#include "xlsxwriter.h"

int main() {

lxw_workbook *workbook = workbook_new("test_macro04.xlsm");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "Foo");

workbook_add_signed_vba_project(workbook,
"images/vbaProject05.bin",
"images/vbaProject05Signature.bin");

worksheet_set_column(worksheet, COLS("A:A"), 30, NULL);
worksheet_write_string(worksheet, 2, 0, "Press the button to say hello.", NULL);

lxw_button_options options = { .caption = "Press Me", .macro = "say_hello",
.width = 80, .height = 30 };

worksheet_insert_button(worksheet, 2, 1, &options);

return workbook_close(workbook);
}
3 changes: 3 additions & 0 deletions test/functional/test_vba.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ def test_macro02(self):

def test_macro03(self):
self.run_exe_test('test_macro03', 'macro03.xlsm')

def test_macro04(self):
self.run_exe_test('test_macro04', 'macro04.xlsm')
Binary file added test/functional/xlsx_files/macro04.xlsm
Binary file not shown.

0 comments on commit eb76eed

Please sign in to comment.