From 83c947462a2f5b599003c36a182f634a7f7e92ee Mon Sep 17 00:00:00 2001 From: Ryan Morlok Date: Sun, 17 Mar 2013 00:08:06 -0500 Subject: [PATCH] added input sanitation for split page generation when accepting a custom outfile file name format. Added unit tests for various file name generation scenarios. --- pdf2htmlEX.1.in | 6 +- src/HTMLRenderer/general.cc | 4 +- src/pdf2htmlEX.cc | 20 +++- src/util/path.cc | 16 +++ src/util/path.h | 11 ++ test/test_data/1-page.pdf | Bin 0 -> 12378 bytes test/test_data/2-pages.pdf | Bin 0 -> 13114 bytes test/test_data/3-pages.pdf | Bin 0 -> 13901 bytes test/test_naming.py | 227 ++++++++++++++++++++++++++++++++++++ 9 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 test/test_data/1-page.pdf create mode 100644 test/test_data/2-pages.pdf create mode 100644 test/test_data/3-pages.pdf create mode 100644 test/test_naming.py diff --git a/pdf2htmlEX.1.in b/pdf2htmlEX.1.in index 14d8917..b0bddfc 100644 --- a/pdf2htmlEX.1.in +++ b/pdf2htmlEX.1.in @@ -65,9 +65,9 @@ You need to modify the manifest if you do not want outline embedded. .TP .B --split-pages <0|1> (Default: 0) -If turned on, pages will be stored into separated files named as 0.page, 1.page, ... +If turned on, pages will be stored into separated files. By defualt, these files will be named as 0.page, 1.page, ..., however the name of the files can be customized by adding a %d marker in the to specify how the page should be used to generate the name. E.g. p%d.page yeilding p1.page, p2.page ... or p%03d.page yielding p001.page, p002.page etc. Only %d may be used, no other formatting markers. -Also the css and outline will be stored into separated files, and the will be no .html generated. +Also the css and outline will be stored into separated files, and there will be no .html generated. This switch is useful if you want pages to be loaded separately & dynamically -- in which case you need to compose the page yourself, and a supporting backend might be necessary. @@ -83,7 +83,7 @@ If it's empty, the file name will be determined automatically. .TP .B --outline-filename (Default: ) -Specify the filename of the generated outline file, if not embedded. +Specify the filename of the generated outline file, if not embedded. If it's empty, the file name will be determined automatically. diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index d23e1f0..bd6b196 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -101,8 +101,8 @@ void HTMLRenderer::process(PDFDoc *doc) if(param->split_pages) { - auto page_template_fn = str_fmt("%s/%s", param->dest_dir.c_str(), param->output_filename.c_str()); - auto page_fn = str_fmt(page_template_fn, i); + auto filled_template_filename = str_fmt(param->output_filename.c_str(), i); + auto page_fn = str_fmt("%s/%s", param->dest_dir.c_str(), string((char*)filled_template_filename).c_str()); f_pages.fs.open((char*)page_fn, ofstream::binary); if(!f_pages.fs) throw string("Cannot open ") + (char*)page_fn + " for writing"; diff --git a/src/pdf2htmlEX.cc b/src/pdf2htmlEX.cc index 01ae39f..54f79bb 100644 --- a/src/pdf2htmlEX.cc +++ b/src/pdf2htmlEX.cc @@ -216,7 +216,7 @@ int main(int argc, char **argv) if(get_suffix(param.input_filename) == ".pdf") { if(param.split_pages) - param.output_filename = s.substr(0, s.size() - 4) + "%d.page"; + param.output_filename = sanitize_filename(s.substr(0, s.size() - 4) + "%d.page", true); else param.output_filename = s.substr(0, s.size() - 4) + ".html"; @@ -224,16 +224,26 @@ int main(int argc, char **argv) else { if(param.split_pages) - param.output_filename = s + "%d.page"; + param.output_filename = sanitize_filename(s + "%d.page", true); else param.output_filename = s + ".html"; } } - else if(param.split_pages && !std::regex_match(param.output_filename, std::regex("^.*%[0-9]*d.*$"))) + else if(param.split_pages) { - const string suffix = get_suffix(param.output_filename); - param.output_filename = param.output_filename.substr(0, param.output_filename.size() - suffix.size()) + "%d" + suffix; + // Need to make sure we have a page number placeholder in the filename + if(!std::regex_match(param.output_filename, std::regex("^.*%[0-9]*d.*$"))) + { + // Inject the placeholder just before the file extension + const string suffix = get_suffix(param.output_filename); + param.output_filename = sanitize_filename(param.output_filename.substr(0, param.output_filename.size() - suffix.size()) + "%d" + suffix, true); + } + else + { + // Already have the placeholder, just make sure the name is safe. + param.output_filename = sanitize_filename(param.output_filename, true); + } } if(param.css_filename.empty()) { diff --git a/src/util/path.cc b/src/util/path.cc index ce80a4f..5c8e1d6 100644 --- a/src/util/path.cc +++ b/src/util/path.cc @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -39,6 +40,21 @@ void create_directories(const string & path) } } +string sanitize_filename(const string & filename, bool allow_single_format_number) +{ + // First, escape all %'s to make safe for use in printf. + string sanitized = std::regex_replace(filename, std::regex("%"), "%%"); + + if(allow_single_format_number) + { + // A single %d or %0xd is allowed in the input. + sanitized = std::regex_replace(sanitized, std::regex("%%([0-9]*)d"), "%$1d", std::regex_constants::format_first_only); + } + + return sanitized; +} + + bool is_truetype_suffix(const string & suffix) { return (suffix == ".ttf") || (suffix == ".ttc") || (suffix == ".otf"); diff --git a/src/util/path.h b/src/util/path.h index 4f82a8e..c16fb91 100644 --- a/src/util/path.h +++ b/src/util/path.h @@ -19,5 +19,16 @@ bool is_truetype_suffix(const std::string & suffix); std::string get_filename(const std::string & path); std::string get_suffix(const std::string & path); +/** + * Function to sanitize a filename so that it can be eventually safely used in a printf statement. + * + * @param filename the filename to be sanitized. + * @param allow_single_form_number boolean flag indicatin if a single format (e.g. %d) should be allowed + * in the filename for use in templating of pages. e.g. page%02d.html is ok. + * + * @return the sanitized filename. + */ +std::string sanitize_filename(const std::string & filename, bool allow_single_format_number); + } //namespace pdf2htmlEX #endif //PATH_H__ diff --git a/test/test_data/1-page.pdf b/test/test_data/1-page.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f5228186f3a942d9a77530e9747bbc9473421c97 GIT binary patch literal 12378 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 sys.stderr, "Command return code %d: %s" % (return_code, ' '.join(cmd)) + + return return_code + +def execute_pdf2htmlex_and_get_files(args): + """ + Execute the pdf2htmlEX with the specified arguments, and get the names of the output files. Will automatically create + a temporary directory for the output, pass that as the output dir to pdf2htmlEX, determine the files generated, and + clean up the temporary directory afterwards. + + :type args: list of values + :param args: list of arguments to pass to executable. First part of each tuple is the argument, second part is the value. + + :rtype: list of str + :return: List of the file names that were generated as output in alphabetical order. None if the command does not execute successfully. + """ + temp_dir = tempfile.mkdtemp() + + try: + if execute_pdf2htmlex_with_args(['--dest-dir', temp_dir] + args) != 0: + return None + + files = os.listdir(temp_dir) + files.sort() + return files + finally: + shutil.rmtree(path=temp_dir, ignore_errors=True) + +def path_to_test_file(filename): + """ + Retrieve an absolute path to the specified test file. + + :type filename: + :param filename: the name of the test file to get the path to + + :rtype: str + :returns: the full path to the test file + """ + return os.path.abspath(os.path.join(os.path.dirname(__file__), TEST_DATA_DIR, filename)) + +class OutputNamingTests(unittest.TestCase): + def test_generate_single_html_default_name_single_page_pdf(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('1-page.pdf') + ]) + self.assertEquals(files, ['1-page.html']) + + def test_generate_single_html_default_name_multiple_page_pdf(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf') + ]) + self.assertEquals(files, ['2-pages.html']) + + def test_generate_single_html_specify_name_single_page_pdf(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('1-page.pdf'), + 'foo.html' + ]) + self.assertEquals(files, ['foo.html']) + + def test_generate_single_html_specify_name_multiple_page_pdf(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf'), + 'foo.html' + ]) + self.assertEquals(files, ['foo.html']) + + def test_generate_split_pages_default_name_single_page(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('1-page.pdf') + ]) + self.assertEquals(files, sorted(['1-page.css', '1-page.outline', '1-page1.page'])) + + def test_generate_split_pages_default_name_multiple_pages(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf') + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', '3-pages1.page', '3-pages2.page', '3-pages3.page'])) + + def test_generate_split_pages_specify_name_single_page(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('1-page.pdf'), + 'foo.xyz' + ]) + self.assertEquals(files, sorted(['1-page.css', '1-page.outline', 'foo1.xyz'])) + + def test_generate_split_pages_specify_name_multiple_pages(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'foo.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'foo1.xyz', 'foo2.xyz', 'foo3.xyz'])) + + def test_generate_split_pages_specify_name_formatter_multiple_pages(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'fo%do.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'fo1o.xyz', 'fo2o.xyz', 'fo3o.xyz'])) + + def test_generate_split_pages_specify_name_formatter_with_padded_zeros_multiple_pages(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'fo%03do.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'fo001o.xyz', 'fo002o.xyz', 'fo003o.xyz'])) + + def test_generate_split_pages_specify_name_only_first_formatter_gets_taken(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'f%do%do.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'f1o%do.xyz', 'f2o%do.xyz', 'f3o%do.xyz'])) + + def test_generate_split_pages_specify_name_only_percent_d_is_used_percent_s(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'f%soo.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'f%soo1.xyz', 'f%soo2.xyz', 'f%soo3.xyz'])) + + def test_generate_split_pages_specify_name_only_percent_d_is_used_percent_p(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'f%poo.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'f%poo1.xyz', 'f%poo2.xyz', 'f%poo3.xyz'])) + + + def test_generate_split_pages_specify_name_only_percent_d_is_used_percent_n(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'f%noo.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'f%noo1.xyz', 'f%noo2.xyz', 'f%noo3.xyz'])) + + def test_generate_split_pages_specify_name_only_percent_d_is_used_percent_percent(self): + files = execute_pdf2htmlex_and_get_files([ + '--split-pages', 1, + path_to_test_file('3-pages.pdf'), + 'f%%oo.xyz' + ]) + self.assertEquals(files, sorted(['3-pages.css', '3-pages.outline', 'f%%oo1.xyz', 'f%%oo2.xyz', 'f%%oo3.xyz'])) + + def test_generate_single_html_name_specified_format_characters_percent_d(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf'), + 'foo%d.html' + ]) + self.assertEquals(files, ['foo%d.html']) + + def test_generate_single_html_name_specified_format_characters_percent_p(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf'), + 'foo%p.html' + ]) + self.assertEquals(files, ['foo%p.html']) + + def test_generate_single_html_name_specified_format_characters_percent_n(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf'), + 'foo%n.html' + ]) + self.assertEquals(files, ['foo%n.html']) + + def test_generate_single_html_name_specified_format_characters_percent_percent(self): + files = execute_pdf2htmlex_and_get_files([ + path_to_test_file('2-pages.pdf'), + 'foo%%.html' + ]) + self.assertEquals(files, ['foo%%.html']) + +if __name__=="__main__": + if not os.path.isfile(PDF2HTMLEX_PATH) or not os.access(PDF2HTMLEX_PATH, os.X_OK): + print >> sys.stderr, "Cannot locate pdf2htmlEX executable. Make sure source was built before running this test." + exit(1) + + suite = unittest.loader.TestLoader().loadTestsFromTestCase(OutputNamingTests) + unittest.TextTestRunner(verbosity=2).run(suite) \ No newline at end of file