From 5993c3e83c6f83b36045c75a03ffb1da3d1d283c Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 29 Jul 2017 20:19:54 -0400 Subject: [PATCH] Detect input file = output file (fixes #29) --- ChangeLog | 3 ++ include/qpdf/QUtil.hh | 3 ++ libqpdf/QUtil.cc | 50 +++++++++++++++++++++++++++++++++ libtests/qtest/qutil/other-file | 1 + libtests/qtest/qutil/qutil.out | 7 +++++ libtests/qutil.cc | 32 +++++++++++++++++++++ qpdf/qpdf.cc | 6 ++++ qpdf/qpdf.testcov | 1 + qpdf/qtest/qpdf.test | 6 +++- 9 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 libtests/qtest/qutil/other-file diff --git a/ChangeLog b/ChangeLog index ac32ea25..8d1cf56a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2017-07-29 Jay Berkenbilt + * Detect when input file and output file are the same and exit to + avoid overwriting and losing input file. Fixes #29. + * When passing multiple inspection arguments, run --check first, and defer exit until after all the checks have been run. This makes it possible to force operations such as --show-xref to be diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh index eba4ef61..98784a37 100644 --- a/include/qpdf/QUtil.hh +++ b/include/qpdf/QUtil.hh @@ -74,6 +74,9 @@ namespace QUtil QPDF_DLL qpdf_offset_t tell(FILE* stream); + QPDF_DLL + bool same_file(char const* name1, char const* name2); + QPDF_DLL char* copy_string(std::string const&); diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc index 86de07f2..eed8d276 100644 --- a/libqpdf/QUtil.cc +++ b/libqpdf/QUtil.cc @@ -24,6 +24,7 @@ #include #else #include +#include #endif std::string @@ -188,6 +189,55 @@ QUtil::tell(FILE* stream) #endif } +bool +QUtil::same_file(char const* name1, char const* name2) +{ + if ((name1 == 0) || (strlen(name1) == 0) || + (name2 == 0) || (strlen(name2) == 0)) + { + return false; + } +#ifdef _WIN32 + HANDLE fh1 = CreateFile(name1, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE fh2 = CreateFile(name2, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + BY_HANDLE_FILE_INFORMATION fi1; + BY_HANDLE_FILE_INFORMATION fi2; + bool same = false; + if ((fh1 != INVALID_HANDLE_VALUE) && + (fh2 != INVALID_HANDLE_VALUE) && + GetFileInformationByHandle(fh1, &fi1) && + GetFileInformationByHandle(fh2, &fi2) && + (fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber) && + (fi1.nFileIndexLow == fi2.nFileIndexLow) && + (fi1.nFileIndexHigh == fi2.nFileIndexHigh)) + { + same = true; + } + if (fh1 != INVALID_HANDLE_VALUE) + { + CloseHandle(fh1); + } + if (fh2 != INVALID_HANDLE_VALUE) + { + CloseHandle(fh2); + } + return same; +#else + struct stat st1; + struct stat st2; + if ((stat(name1, &st1) == 0) && + (stat(name2, &st2) == 0) && + (st1.st_ino == st2.st_ino) && + (st1.st_dev == st2.st_dev)) + { + return true; + } +#endif + return false; +} + char* QUtil::copy_string(std::string const& str) { diff --git a/libtests/qtest/qutil/other-file b/libtests/qtest/qutil/other-file new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/libtests/qtest/qutil/other-file @@ -0,0 +1 @@ +test diff --git a/libtests/qtest/qutil/qutil.out b/libtests/qtest/qutil/qutil.out index 4273fe11..453185d4 100644 --- a/libtests/qtest/qutil/qutil.out +++ b/libtests/qtest/qutil/qutil.out @@ -35,3 +35,10 @@ quack1 quack2 quack3 quack4 +---- +file1: -qutil.out-, file2: -./qutil.out-; same: 1: PASS +file1: -qutil.out-, file2: -qutil.out-; same: 1: PASS +file1: -qutil.out-, file2: -other-file-; same: 0: PASS +file1: -qutil.out-, file2: --; same: 0: PASS +file1: -qutil.out-, file2: -(null)-; same: 0: PASS +file1: --, file2: -qutil.out-; same: 0: PASS diff --git a/libtests/qutil.cc b/libtests/qutil.cc index b0134e79..5e562b89 100644 --- a/libtests/qutil.cc +++ b/libtests/qutil.cc @@ -140,6 +140,36 @@ void get_whoami_test() print_whoami("a\\b\\c\\quack4.exe"); } +void assert_same_file(char const* file1, char const* file2, bool expected) +{ + bool actual = QUtil::same_file(file1, file2); + std::cout << "file1: -" << (file1 ? file1 : "(null)") << "-, file2: -" + << (file2 ? file2 : "(null)") << "-; same: " + << actual << ": " << ((actual == expected) ? "PASS" : "FAIL") + << std::endl; +} + +void same_file_test() +{ + try + { + fclose(QUtil::safe_fopen("qutil.out", "r")); + fclose(QUtil::safe_fopen("other-file", "r")); + } + catch (std::exception) + { + std::cout << "same_file_test expects to have qutil.out and other-file" + " exist in the current directory\n"; + return; + } + assert_same_file("qutil.out", "./qutil.out", true); + assert_same_file("qutil.out", "qutil.out", true); + assert_same_file("qutil.out", "other-file", false); + assert_same_file("qutil.out", "", false); + assert_same_file("qutil.out", 0, false); + assert_same_file("", "qutil.out", false); +} + int main(int argc, char* argv[]) { try @@ -155,6 +185,8 @@ int main(int argc, char* argv[]) to_utf8_test(); std::cout << "----" << std::endl; get_whoami_test(); + std::cout << "----" << std::endl; + same_file_test(); } catch (std::exception& e) { diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index ae365c70..a1388692 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -1361,6 +1361,12 @@ int main(int argc, char* argv[]) usage("no output file may be given for this option"); } + if (QUtil::same_file(infilename, outfilename)) + { + QTC::TC("qpdf", "qpdf same file error"); + usage("input file and output file are the same; this would cause input file to be lost"); + } + try { QPDF pdf; diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index d1ddd55d..5f810b03 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -284,3 +284,4 @@ QPDFWriter preserve unreferenced standard 0 QPDFObjectHandle non-stream in parsecontent 0 QPDFObjectHandle errors in parsecontent 0 QPDF stream with non-space 0 +qpdf same file error 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 031c33a9..f0205e1d 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -206,7 +206,7 @@ $td->runtest("remove page we don't have", show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 93; +$n_tests += 94; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -641,6 +641,10 @@ $td->runtest("dump corrected bad xref", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); +$td->runtest("don't overwrite self", + {$td->COMMAND => "qpdf a.pdf a.pdf"}, + {$td->REGEXP => "input file and output file are the same.*", + $td->EXIT_STATUS => 2}); show_ntests(); # ----------