From d492bb0a90e30c8c57f36434479ddb708d322e79 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 31 Aug 2019 15:11:11 -0400 Subject: [PATCH] Add --replace-input option (fixes #321) --- manual/qpdf-manual.xml | 34 ++++++- qpdf/qpdf.cc | 118 +++++++++++++++++++++-- qpdf/qtest/qpdf.test | 43 ++++++++- qpdf/qtest/qpdf/bad-jpeg-show.out | 2 +- qpdf/qtest/qpdf/empty-object.out | 2 +- qpdf/qtest/qpdf/replace-input.pdf | Bin 0 -> 743 bytes qpdf/qtest/qpdf/replace-warn.out | 3 + qpdf/qtest/qpdf/warn-replace.pdf | Bin 0 -> 16504 bytes qpdf/qtest/qpdf/xref-with-short-size.out | 2 +- 9 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 qpdf/qtest/qpdf/replace-input.pdf create mode 100644 qpdf/qtest/qpdf/replace-warn.out create mode 100644 qpdf/qtest/qpdf/warn-replace.pdf diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index bfdefc41..01d1b9cf 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -331,10 +331,12 @@ make does not have to be seekable, even when generating linearized files. Specifying “” as - means to write to standard output. However, you can't specify the - same file as both the input and the output because qpdf reads data - from the input file as it writes to the output file. QPDF attempts - to detect this case and fail without overwriting the output file. + means to write to standard output. If you want to overwrite the + input file with the output, use the option + and omit the output file name. + You can't specify the same file as both the input and the output. + If you do this, qpdf will tell you about the + option. Most options require an output file, but some testing or @@ -449,6 +451,21 @@ make + + + + + If specified, the output file name should be omitted. This + option tells qpdf to replace the input file with the output. + It does this by writing to + .~qpdf-temp.infilename# + and, when done, overwriting the input file with the temporary + file. If there were any warnings, the original input is saved + as + infilename.~qpdf-orig. + + + @@ -4419,6 +4436,15 @@ print "\n"; CLI Enhancements + + + The option may be given in + place of an output file name. This causes qpdf to overwrite + the input file with the output. See the description of + in for more details. + + The instructs diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index 4b28f73a..8bb1ce48 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -180,6 +181,7 @@ struct Options overlay("overlay"), under_overlay(0), require_outfile(true), + replace_input(false), infilename(0), outfilename(0) { @@ -283,6 +285,7 @@ struct Options std::vector page_specs; std::map rotations; bool require_outfile; + bool replace_input; char const* infilename; char const* outfilename; }; @@ -712,6 +715,7 @@ class ArgParser void argUOrepeat(char* parameter); void argUOpassword(char* parameter); void argEndUnderOverlay(); + void argReplaceInput(); void usage(std::string const& message); void checkCompletion(); @@ -940,6 +944,7 @@ ArgParser::initOptionTable() &ArgParser::argIiMinBytes, "minimum-bytes"); (*t)["overlay"] = oe_bare(&ArgParser::argOverlay); (*t)["underlay"] = oe_bare(&ArgParser::argUnderlay); + (*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput); t = &this->encrypt40_option_table; (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt); @@ -1080,6 +1085,9 @@ ArgParser::argHelp() << "will be interpreted as an argument. No interpolation is done. Line\n" << "terminators are stripped. @- can be specified to read from standard input.\n" << "\n" + << "The output file can be - to indicate writing to standard output, or it can\n" + << "be --replace-input to cause qpdf to replace the input file with the output.\n" + << "\n" << "Note that when contradictory options are provided, whichever options are\n" << "provided last take precedence.\n" << "\n" @@ -1097,6 +1105,8 @@ ArgParser::argHelp() << "--progress give progress indicators while writing output\n" << "--no-warn suppress warnings\n" << "--linearize generated a linearized (web optimized) file\n" + << "--replace-input use in place of specifying an output file; qpdf will\n" + << " replace the input file with the output\n" << "--copy-encryption=file copy encryption parameters from specified file\n" << "--encryption-file-password=password\n" << " password used to open the file from which encryption\n" @@ -2316,6 +2326,12 @@ ArgParser::argEndUnderOverlay() o.under_overlay = 0; } +void +ArgParser::argReplaceInput() +{ + o.replace_input = true; +} + void ArgParser::handleArgFileArguments() { @@ -3048,15 +3064,28 @@ ArgParser::doFinalChecks() { usage("missing -- at end of options"); } + if (o.replace_input) + { + if (o.outfilename) + { + usage("--replace-input may not be used when" + " an output file is specified"); + } + else if (o.split_pages) + { + usage("--split-pages may not be used with --replace-input"); + } + } if (o.infilename == 0) { usage("an input file name is required"); } - else if (o.require_outfile && (o.outfilename == 0)) + else if (o.require_outfile && (o.outfilename == 0) && (! o.replace_input)) { usage("an output file name is required; use - for standard output"); } - else if ((! o.require_outfile) && (o.outfilename != 0)) + else if ((! o.require_outfile) && + ((o.outfilename != 0) || o.replace_input)) { usage("no output file may be given for this option"); } @@ -3065,7 +3094,8 @@ ArgParser::doFinalChecks() o.externalize_inline_images = true; } - if (o.require_outfile && (strcmp(o.outfilename, "-") == 0)) + if (o.require_outfile && o.outfilename && + (strcmp(o.outfilename, "-") == 0)) { if (o.split_pages) { @@ -3088,7 +3118,7 @@ ArgParser::doFinalChecks() { QTC::TC("qpdf", "qpdf same file error"); usage("input file and output file are the same;" - " this would cause input file to be lost"); + " use --replace-input to intentionally overwrite the input file"); } } @@ -3861,6 +3891,12 @@ static void do_inspection(QPDF& pdf, Options& o) { do_show_pages(pdf, o); } + if ((! pdf.getWarnings().empty()) && (exit_code != EXIT_ERROR)) + { + std::cerr << whoami + << ": operation succeeded with warnings" << std::endl; + exit_code = EXIT_WARNING; + } if (exit_code) { exit(exit_code); @@ -5109,18 +5145,80 @@ static void do_split_pages(QPDF& pdf, Options& o) static void write_outfile(QPDF& pdf, Options& o) { - if (strcmp(o.outfilename, "-") == 0) + std::string temp_out; + if (o.replace_input) + { + // Use a file name that is hidden by default in the OS to + // avoid having it become momentarily visible in a + // graphical file manager or in case it gets left behind + // because of some kind of error. + temp_out = ".~qpdf-temp." + std::string(o.infilename) + "#"; + // o.outfilename will be restored to 0 before temp_out + // goes out of scope. + o.outfilename = temp_out.c_str(); + } + else if (strcmp(o.outfilename, "-") == 0) { o.outfilename = 0; } - QPDFWriter w(pdf, o.outfilename); - set_writer_options(pdf, o, w); - w.write(); - if (o.verbose) + { + // Private scope so QPDFWriter will close the output file + QPDFWriter w(pdf, o.outfilename); + set_writer_options(pdf, o, w); + w.write(); + } + if (o.verbose && o.outfilename) { std::cout << whoami << ": wrote file " << o.outfilename << std::endl; } + if (o.replace_input) + { + o.outfilename = 0; + } + if (o.replace_input) + { + // We must close the input before we can rename files + pdf.closeInputSource(); + std::string backup; + bool warnings = pdf.anyWarnings(); + if (warnings) + { + // If there are warnings, the user may care about this + // file, so give it a non-hidden name that will be + // lexically grouped with the original file. + backup = std::string(o.infilename) + ".~qpdf-orig"; + } + else + { + backup = ".~qpdf-orig." + std::string(o.infilename) + "#"; + } + QUtil::rename_file(o.infilename, backup.c_str()); + QUtil::rename_file(temp_out.c_str(), o.infilename); + if (warnings) + { + std::cerr << whoami + << ": there are warnings; original file kept in " + << backup << std::endl; + } + else + { + try + { + QUtil::remove_file(backup.c_str()); + } + catch (QPDFSystemError& e) + { + std::cerr + << whoami + << ": unable to delete original file (" + << e.what() << ");" + << " original file left in " << backup + << ", but the input was successfully replaced" + << std::endl; + } + } + } } int realmain(int argc, char* argv[]) @@ -5156,7 +5254,7 @@ int realmain(int argc, char* argv[]) handle_under_overlay(pdf, o); handle_transformations(pdf, o); - if (o.outfilename == 0) + if ((o.outfilename == 0) && (! o.replace_input)) { do_inspection(pdf, o); } diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 41c94519..460685d9 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -189,6 +189,47 @@ foreach my $d (['auto-ü', 1], ['auto-öπ', 2]) $td->NORMALIZE_NEWLINES); } +show_ntests(); +# ---------- +$td->notify("--- Replace Input ---"); +$n_tests += 8; + +# Use Unicode file names to test replace input so we can be sure it +# works for that case. +$td->runtest("create unicode filenames", + {$td->COMMAND => "test_unicode_filenames"}, + {$td->STRING => "created Unicode filenames\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +foreach my $d (['auto-ü', 1], ['auto-öπ', 2]) +{ + my ($u, $n) = @$d; + $td->runtest("replace input $u", + {$td->COMMAND => "qpdf --deterministic-id" . + " --object-streams=generate --replace-input $u.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + $td->runtest("check output ($u)", + {$td->FILE => "$u.pdf"}, + {$td->FILE => "replace-input.pdf"}, + $td->NORMALIZE_NEWLINES); +} + +system("cp xref-with-short-size.pdf auto-warn.pdf") == 0 or die; +$td->runtest("replace input with warnings", + {$td->COMMAND => + "qpdf --deterministic-id --replace-input auto-warn.pdf"}, + {$td->FILE => "replace-warn.out", $td->EXIT_STATUS => 3}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("check output", + {$td->FILE => "auto-warn.pdf"}, + {$td->FILE => "warn-replace.pdf"}); +$td->runtest("check orig output", + {$td->FILE => "auto-warn.pdf.~qpdf-orig"}, + {$td->FILE => "xref-with-short-size.pdf"}); + show_ntests(); # ---------- $td->notify("--- Final Version ---"); @@ -4233,5 +4274,5 @@ sub get_md5_checksum sub cleanup { system("rm -rf *.ps *.pnm ?.pdf ?.qdf *.enc* tif1 tif2 tiff-cache"); - system("rm -rf *split-out* ???-kfo.pdf *.tmpout \@file.pdf auto-*.pdf"); + system("rm -rf *split-out* ???-kfo.pdf *.tmpout \@file.pdf auto-*"); } diff --git a/qpdf/qtest/qpdf/bad-jpeg-show.out b/qpdf/qtest/qpdf/bad-jpeg-show.out index f1b0bcc7..ca178e50 100644 --- a/qpdf/qtest/qpdf/bad-jpeg-show.out +++ b/qpdf/qtest/qpdf/bad-jpeg-show.out @@ -1,2 +1,2 @@ WARNING: bad-jpeg.pdf (offset 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77 -qpdf: operation succeeded with warnings; resulting file may have some problems +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/empty-object.out b/qpdf/qtest/qpdf/empty-object.out index 7ebfe52c..e2181c6e 100644 --- a/qpdf/qtest/qpdf/empty-object.out +++ b/qpdf/qtest/qpdf/empty-object.out @@ -1,3 +1,3 @@ WARNING: empty-object.pdf (object 7 0, offset 575): empty object treated as null null -qpdf: operation succeeded with warnings; resulting file may have some problems +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/replace-input.pdf b/qpdf/qtest/qpdf/replace-input.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b0a7e2e9d16fee0f0272ddd7f1f8a2764d56186f GIT binary patch literal 743 zcmY!laBZ^4|D>$ol3WFSpVYkck_-hS zBVz@9x6GW9)FL3AlUS1KlA4^K0#xXyU<#5cDlSnlGE=a#<0>vGN=?k=s+i;2AJ~1! zK%nLRU(wI}mt1oFCmdR--c->c_d~(%$*rX&N%$K$ zer(hXnWo$xRAe&W`Awc{#DqSDKbUXIx1XN z^8_6{_`aDgJae7GlLR-}tH!ghjI~XGM=ycdRvu%wQlD{+eR_gEjD!(+O z@J*fbqT@W4E~;Ygdl~vC$1Y9HOM!Y6$N~kOIZ_Zp1I@$&dzc{yQZd)VpnwbI#vukq zfgGO9o6hz&oN!Q7VLfq;f4dBwHa77ZH@229Gvl zBkV5Fhs0h$Vo`3f0w~g*^K(jb^NJNr6!Zg%Qd2UMOY(~p42^*Csh}UO5UpUSU%@me>W}wVh9TT literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/replace-warn.out b/qpdf/qtest/qpdf/replace-warn.out new file mode 100644 index 00000000..09a9261a --- /dev/null +++ b/qpdf/qtest/qpdf/replace-warn.out @@ -0,0 +1,3 @@ +WARNING: auto-warn.pdf (xref stream, offset 16227): Cross-reference stream data has the wrong size; expected = 52; actual = 56 +qpdf: there are warnings; original file kept in auto-warn.pdf.~qpdf-orig +qpdf: operation succeeded with warnings; resulting file may have some problems diff --git a/qpdf/qtest/qpdf/warn-replace.pdf b/qpdf/qtest/qpdf/warn-replace.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7d1bef39b2c1780ff23766014a46bd2565f1ebd4 GIT binary patch literal 16504 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�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