diff --git a/.indent.pro b/.indent.pro index 05e3e6f6..a81d71c1 100644 --- a/.indent.pro +++ b/.indent.pro @@ -64,8 +64,10 @@ -T lxw_heading_pair -T lxw_merged_range -T lxw_packager +-T lxw_panes -T lxw_part_name -T lxw_print_area +-T lxw_protection -T lxw_rel_tuple -T lxw_relationships -T lxw_repeat_cols @@ -73,6 +75,7 @@ -T lxw_row -T lxw_row_col_options -T lxw_row_t +-T lxw_selection -T lxw_sst -T lxw_styles -T lxw_theme diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 9379d1f3..2ed2ccd9 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -2188,6 +2188,9 @@ void worksheet_hide_zero(lxw_worksheet *worksheet); */ void worksheet_set_tab_color(lxw_worksheet *worksheet, lxw_color_t color); +void worksheet_protect(lxw_worksheet *worksheet, char *password, + lxw_protection *options); + lxw_worksheet *_new_worksheet(lxw_worksheet_init_data *init_data); void _free_worksheet(lxw_worksheet *worksheet); void _worksheet_assemble_xml_file(lxw_worksheet *worksheet); diff --git a/src/styles.c b/src/styles.c index d2b5664b..06c9cbd1 100644 --- a/src/styles.c +++ b/src/styles.c @@ -212,7 +212,7 @@ _write_font_name(lxw_styles *self, const char *font_name) _INIT_ATTRIBUTES(); - if (strlen(font_name)) + if (*font_name) _PUSH_ATTRIBUTES_STR("val", font_name); else _PUSH_ATTRIBUTES_STR("val", LXW_DEFAULT_FONT_NAME); @@ -250,7 +250,7 @@ _write_font_scheme(lxw_styles *self, const char *font_scheme) _INIT_ATTRIBUTES(); - if (strlen(font_scheme)) + if (*font_scheme) _PUSH_ATTRIBUTES_STR("val", font_scheme); else _PUSH_ATTRIBUTES_STR("val", "minor"); @@ -350,7 +350,7 @@ _write_font(lxw_styles *self, lxw_format *format) /* Only write the scheme element for the default font type if it * is a hyperlink. */ - if ((!strlen(format->font_name) + if ((!*format->font_name || strcmp(LXW_DEFAULT_FONT_NAME, format->font_name) == 0) && !format->hyperlink) { _write_font_scheme(self, format->font_scheme); diff --git a/src/workbook.c b/src/workbook.c index 2d218f7f..5a2ece82 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -335,7 +335,7 @@ _prepare_num_formats(lxw_workbook *self) /* Check if there is a user defined number format string. */ num_format = format->num_format; - if (strlen(num_format)) { + if (*num_format) { /* Look up the num_format in the hash table. */ hash_element = _hash_key_exists(num_formats, num_format, strlen(num_format)); diff --git a/src/worksheet.c b/src/worksheet.c index 77c04657..617983aa 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -653,6 +653,38 @@ _cell_cmp(lxw_cell *cell1, lxw_cell *cell2) return 0; } +/* + * Hash a worksheet password. Based on the algorithm provided by Daniel Rentz + * of OpenOffice. + */ +uint16_t +_hash_password(char *password) +{ + uint8_t count; + uint8_t i; + uint16_t hash = 0x0000; + + count = strlen(password); + + for (i = 0; i < count; i++) { + uint32_t low_15; + uint32_t high_15; + uint32_t letter = password[i] << (i + 1); + + low_15 = letter & 0x7fff; + high_15 = letter & (0x7fff << 15); + high_15 = high_15 >> 15; + letter = low_15 | high_15; + + hash ^= letter; + } + + hash ^= count; + hash ^= 0xCE4B; + + return hash; +} + /***************************************************************************** * * XML functions. @@ -1016,7 +1048,7 @@ _worksheet_write_split_panes(lxw_worksheet *self) * Write the element. */ STATIC void -_worksheet_write_selection(lxw_worksheet *self, lxw_selection * selection) +_worksheet_write_selection(lxw_worksheet *self, lxw_selection *selection) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -2374,7 +2406,7 @@ worksheet_write_string(lxw_worksheet *self, char *string_copy; int8_t err; - if (!string || !strlen(string)) { + if (!string || !*string) { /* Treat a NULL or empty string with formatting as a blank cell. */ /* Null strings without formats should be ignored. */ if (format) @@ -2633,7 +2665,7 @@ worksheet_write_url_opt(lxw_worksheet *self, size_t i; enum cell_types link_type = HYPERLINK_URL; - if (!url || !strlen(url)) + if (!url || !*url) return -LXW_ERROR_WORKSHEET_NULL_STRING_IGNORED; /* Check the Excel limit of URLS per worksheet. */ @@ -3660,3 +3692,28 @@ worksheet_set_tab_color(lxw_worksheet *self, lxw_color_t color) { self->tab_color = color; } + +/* + * Set the worksheet protection flags to prevent modification of worksheet + * objects. + */ +void +worksheet_protect(lxw_worksheet *self, char *password, + lxw_protection *options) +{ + struct lxw_protection *protect = &self->protection; + + /* Copy any user parameters to the internal structure. */ + if (options) + memcpy(protect, options, sizeof(lxw_protection)); + + /* Zero the hash storage in case of copied initialization data. */ + protect->hash[0] = '\0'; + + if (password) { + uint16_t hash = _hash_password(password); + lxw_snprintf(protect->hash, 5, "%X", hash); + } + + protect->is_configured = LXW_TRUE; +} diff --git a/test/functional/src/test_protect01.c b/test/functional/src/test_protect01.c new file mode 100644 index 00000000..56f93b0d --- /dev/null +++ b/test/functional/src/test_protect01.c @@ -0,0 +1,29 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_protect01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_format *unlocked = workbook_add_format(workbook); + format_set_unlocked(unlocked); + + lxw_format *hidden = workbook_add_format(workbook); + format_set_unlocked(hidden); + format_set_hidden(hidden); + + worksheet_write_number(worksheet, CELL("A1"), 1 , NULL); + worksheet_write_number(worksheet, CELL("A2"), 2, unlocked); + worksheet_write_number(worksheet, CELL("A3"), 3, hidden); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_protect02.c b/test/functional/src/test_protect02.c new file mode 100644 index 00000000..ee70a774 --- /dev/null +++ b/test/functional/src/test_protect02.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_protect02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_format *unlocked = workbook_add_format(workbook); + format_set_unlocked(unlocked); + + lxw_format *hidden = workbook_add_format(workbook); + format_set_unlocked(hidden); + format_set_hidden(hidden); + + worksheet_protect(worksheet, NULL, NULL); + + worksheet_write_number(worksheet, CELL("A1"), 1 , NULL); + worksheet_write_number(worksheet, CELL("A2"), 2, unlocked); + worksheet_write_number(worksheet, CELL("A3"), 3, hidden); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_protect03.c b/test/functional/src/test_protect03.c new file mode 100644 index 00000000..46fa209f --- /dev/null +++ b/test/functional/src/test_protect03.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_protect03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_format *unlocked = workbook_add_format(workbook); + format_set_unlocked(unlocked); + + lxw_format *hidden = workbook_add_format(workbook); + format_set_unlocked(hidden); + format_set_hidden(hidden); + + worksheet_protect(worksheet, "password", NULL); + + worksheet_write_number(worksheet, CELL("A1"), 1 , NULL); + worksheet_write_number(worksheet, CELL("A2"), 2, unlocked); + worksheet_write_number(worksheet, CELL("A3"), 3, hidden); + + return workbook_close(workbook); +} diff --git a/test/functional/test_protect.py b/test/functional/test_protect.py new file mode 100644 index 00000000..fb4922db --- /dev/null +++ b/test/functional/test_protect.py @@ -0,0 +1,23 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_protect01(self): + self.run_exe_test('test_protect01') + + def test_protect02(self): + self.run_exe_test('test_protect02') + + def test_protect03(self): + self.run_exe_test('test_protect03') diff --git a/test/functional/xlsx_files/protect01.xlsx b/test/functional/xlsx_files/protect01.xlsx new file mode 100644 index 00000000..81582e7d Binary files /dev/null and b/test/functional/xlsx_files/protect01.xlsx differ diff --git a/test/functional/xlsx_files/protect02.xlsx b/test/functional/xlsx_files/protect02.xlsx new file mode 100644 index 00000000..c46820d0 Binary files /dev/null and b/test/functional/xlsx_files/protect02.xlsx differ diff --git a/test/functional/xlsx_files/protect03.xlsx b/test/functional/xlsx_files/protect03.xlsx new file mode 100644 index 00000000..35f78b18 Binary files /dev/null and b/test/functional/xlsx_files/protect03.xlsx differ diff --git a/test/unit/worksheet/test_worksheet_write_sheet_protection.c b/test/unit/worksheet/test_worksheet_write_sheet_protection.c index c394a2a5..f7b40731 100644 --- a/test/unit/worksheet/test_worksheet_write_sheet_protection.c +++ b/test/unit/worksheet/test_worksheet_write_sheet_protection.c @@ -20,7 +20,6 @@ CTEST(worksheet, write_write_sheet_protection01) { lxw_worksheet *worksheet = _new_worksheet(NULL); worksheet->file = testfile; - //worksheet_protect(worksheet, $password, \%options); worksheet->protection.is_configured = 1; _worksheet_write_sheet_protection(worksheet); @@ -28,3 +27,360 @@ CTEST(worksheet, write_write_sheet_protection01) { _free_worksheet(worksheet); } + + +/* 2. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection02) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_protect(worksheet, "password", NULL); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + +/* 3. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection03) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.no_select_locked_cells = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 4. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection04) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.format_cells = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 5. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection05) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.format_columns = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 6. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection06) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.format_rows = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 7. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection07) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.insert_columns = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 8. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection08) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.insert_rows = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 9. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection09) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.insert_hyperlinks = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 10. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection10) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.delete_columns = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 11. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection11) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.delete_rows = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 12. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection12) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.sort = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 13. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection13) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.autofilter = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 14. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection14) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.pivot_tables = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 15. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection15) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.objects = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 16. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection16) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = {.scenarios = 1}; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 17. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection17) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = { + .format_cells = 1, + .no_select_locked_cells = 1, + .no_select_unlocked_cells = 1, + }; + + worksheet_protect(worksheet, NULL, &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +/* 18. Test the _write_sheet_protection() method. */ +CTEST(worksheet, write_write_sheet_protection18) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + lxw_protection options = { + .objects = 1, + .scenarios = 1, + .format_cells = 1, + .format_columns = 1, + .format_rows = 1, + .insert_columns = 1, + .insert_rows = 1, + .insert_hyperlinks = 1, + .delete_columns = 1, + .delete_rows = 1, + .no_select_locked_cells = 1, + .sort = 1, + .autofilter = 1, + .pivot_tables = 1, + .no_select_unlocked_cells = 1, + }; + + worksheet_protect(worksheet, "drowssap", &options); + _worksheet_write_sheet_protection(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +}