From bc4e2320e7dafea8b6d6b6150c808ed2a98d7d03 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Tue, 1 Feb 2022 08:55:18 -0500 Subject: [PATCH] Add qpdfjob-c.h -- simple C API around parts of QPDFJob --- include/qpdf/qpdfjob-c.h | 79 +++++++++++++++++++++++++ libqpdf/build.mk | 3 +- libqpdf/qpdfjob-c.cc | 50 ++++++++++++++++ manual/qpdf-job.rst | 4 +- qpdf/build.mk | 4 +- qpdf/qpdfjob-ctest.c | 73 +++++++++++++++++++++++ qpdf/qtest/qpdf.test | 30 +++++++++- qpdf/qtest/qpdf/qpdfjob-ctest-wide.pdf | Bin 0 -> 799 bytes qpdf/qtest/qpdf/qpdfjob-ctest.out | 7 +++ qpdf/qtest/qpdf/qpdfjob-ctest1.pdf | Bin 0 -> 799 bytes qpdf/qtest/qpdf/qpdfjob-ctest2.pdf | Bin 0 -> 3379 bytes qpdf/qtest/qpdf/qpdfjob-ctest3.pdf | Bin 0 -> 16504 bytes 12 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 include/qpdf/qpdfjob-c.h create mode 100644 libqpdf/qpdfjob-c.cc create mode 100644 qpdf/qpdfjob-ctest.c create mode 100644 qpdf/qtest/qpdf/qpdfjob-ctest-wide.pdf create mode 100644 qpdf/qtest/qpdf/qpdfjob-ctest.out create mode 100644 qpdf/qtest/qpdf/qpdfjob-ctest1.pdf create mode 100644 qpdf/qtest/qpdf/qpdfjob-ctest2.pdf create mode 100644 qpdf/qtest/qpdf/qpdfjob-ctest3.pdf diff --git a/include/qpdf/qpdfjob-c.h b/include/qpdf/qpdfjob-c.h new file mode 100644 index 00000000..59c42822 --- /dev/null +++ b/include/qpdf/qpdfjob-c.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2005-2021 Jay Berkenbilt + * + * This file is part of qpdf. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Versions of qpdf prior to version 7 were released under the terms + * of version 2.0 of the Artistic License. At your option, you may + * continue to consider qpdf to be licensed under those terms. Please + * see the manual for additional information. + */ + +#ifndef QPDFJOB_C_H +#define QPDFJOB_C_H + +/* + * This file defines a basic "C" API for QPDFJob. See also qpdf-c.h, + * which defines an API that exposes more of the library's API. This + * API is primarily intended to make it simpler for programs in + * languages other than C++ to incorporate functionality that could be + * run directly from the command-line. + */ + +#include +#include +#ifndef QPDF_NO_WCHAR_T +# include +#endif + +/* + * This file provides a minimal wrapper around QPDFJob. See + * examples/qpdf-job.c for an example of its use. + */ + +#ifdef __cplusplus +extern "C" { +#endif + /* This function does the equivalent of running the qpdf + * command-line with the given arguments and returns the exit code + * that qpdf would use. Note that arguments must be UTF8-encoded. + * If calling this from wmain on Windows, use + * qpdfjob_run_from_wide_argv instead. + */ + QPDF_DLL + int qpdfjob_run_from_argv(int argc, char* argv[]); + +#ifndef QPDF_NO_WCHAR_T + /* This function is the same as qpdfjob_run_from_argv except argv + * is encoded with wide characters. This would suitable for + * calling from a Windows wmain function. + */ + QPDF_DLL + int qpdfjob_run_from_wide_argv(int argc, wchar_t* argv[]); +#endif /* QPDF_NO_WCHAR_T */ + + /* This function runs QPDFJob from a job JSON file. See the "QPDF + * Job" section of the manual for details. The JSON string must be + * UTF8-encoded. It returns the error code that qpdf would return + * with the equivalent command-line invocation. + */ + QPDF_DLL + int qpdfjob_run_from_json(char const* json); + +#ifdef __cplusplus +} +#endif + + +#endif /* QPDFJOB_C_H */ diff --git a/libqpdf/build.mk b/libqpdf/build.mk index e16ba5e1..3dedfc71 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -115,7 +115,8 @@ SRCS_libqpdf = \ libqpdf/SecureRandomDataProvider.cc \ libqpdf/SF_FlateLzwDecode.cc \ libqpdf/SparseOHArray.cc \ - libqpdf/qpdf-c.cc + libqpdf/qpdf-c.cc \ + libqpdf/qpdfjob-c.cc ifeq ($(USE_CRYPTO_NATIVE), 1) SRCS_libqpdf += $(CRYPTO_NATIVE) diff --git a/libqpdf/qpdfjob-c.cc b/libqpdf/qpdfjob-c.cc new file mode 100644 index 00000000..7b3f6b60 --- /dev/null +++ b/libqpdf/qpdfjob-c.cc @@ -0,0 +1,50 @@ +#include + +#include +#include +#include + +#include +#include + +int qpdfjob_run_from_argv(int argc, char* argv[]) +{ + auto whoami = QUtil::getWhoami(argv[0]); + QUtil::setLineBuf(stdout); + + QPDFJob j; + try + { + j.initializeFromArgv(argc, argv); + j.run(); + } + catch (std::exception& e) + { + std::cerr << whoami << ": " << e.what() << std::endl; + return QPDFJob::EXIT_ERROR; + } + return j.getExitCode(); +} + +#ifndef QPDF_NO_WCHAR_T +int qpdfjob_run_from_wide_argv(int argc, wchar_t* argv[]) +{ + return QUtil::call_main_from_wmain(argc, argv, qpdfjob_run_from_argv); +} +#endif // QPDF_NO_WCHAR_T + +int qpdfjob_run_from_json(char const* json) +{ + QPDFJob j; + try + { + j.initializeFromJson(json); + j.run(); + } + catch (std::exception& e) + { + std::cerr << "qpdfjob json: " << e.what() << std::endl; + return QPDFJob::EXIT_ERROR; + } + return j.getExitCode(); +} diff --git a/manual/qpdf-job.rst b/manual/qpdf-job.rst index 72e02305..5ee497b1 100644 --- a/manual/qpdf-job.rst +++ b/manual/qpdf-job.rst @@ -14,7 +14,7 @@ executable is available from inside the C++ library using the - Use from the C++ API with ``QPDFJob::initializeFromArgv`` - - Use from the C API with QXXXQ + - Use from the C API with ``qpdfjob_run_from_argv`` from :file:`qpdfjob-c.h` - The job JSON file format @@ -22,7 +22,7 @@ executable is available from inside the C++ library using the - Use from the C++ API with ``QPDFJob::initializeFromJson`` - - Use from the C API with QXXXQ + - Use from the C API with ``qpdfjob_run_from_json`` from :file:`qpdfjob-c.h` - The ``QPDFJob`` C++ API diff --git a/qpdf/build.mk b/qpdf/build.mk index bf1f52f8..36224090 100644 --- a/qpdf/build.mk +++ b/qpdf/build.mk @@ -12,7 +12,9 @@ BINS_qpdf = \ test_tokenizer \ test_unicode_filenames \ test_xref -CBINS_qpdf = qpdf-ctest +CBINS_qpdf = \ + qpdf-ctest \ + qpdfjob-ctest TARGETS_qpdf = $(foreach B,$(BINS_qpdf) $(CBINS_qpdf),qpdf/$(OUTPUT_DIR)/$(call binname,$(B))) diff --git a/qpdf/qpdfjob-ctest.c b/qpdf/qpdfjob-ctest.c new file mode 100644 index 00000000..3de2fa96 --- /dev/null +++ b/qpdf/qpdfjob-ctest.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include + +#ifndef QPDF_NO_WCHAR_T +static void wide_test() +{ + wchar_t* argv[5]; + argv[0] = (wchar_t*)(L"qpdfjob"); + argv[1] = (wchar_t*)(L"minimal.pdf"); + argv[2] = (wchar_t*)(L"a.pdf"); + argv[3] = (wchar_t*)(L"--static-id"); + argv[4] = NULL; + assert(qpdfjob_run_from_wide_argv(4, argv) == 0); + printf("wide test passed\n"); +} +#endif // QPDF_NO_WCHAR_T + +static void run_tests() +{ + /* Be sure to use a different output file for each test. */ + + char* argv[5]; + argv[0] = (char*)("qpdfjob"); + argv[1] = (char*)("minimal.pdf"); + argv[2] = (char*)("a.pdf"); + argv[3] = (char*)("--deterministic-id"); + argv[4] = NULL; + assert(qpdfjob_run_from_argv(4, argv) == 0); + printf("argv test passed\n"); + + assert(qpdfjob_run_from_json("{\n\ + \"inputFile\": \"20-pages.pdf\",\n\ + \"password\": \"user\",\n\ + \"outputFile\": \"b.pdf\",\n\ + \"staticId\": \"\",\n\ + \"decrypt\": \"\",\n\ + \"objectStreams\": \"generate\"\n\ +}") == 0); + printf("json test passed\n"); + + assert(qpdfjob_run_from_json("{\n\ + \"inputFile\": \"xref-with-short-size.pdf\",\n\ + \"outputFile\": \"c.pdf\",\n\ + \"staticId\": \"\",\n\ + \"decrypt\": \"\",\n\ + \"objectStreams\": \"generate\"\n\ +}") == 3); + printf("json warn test passed\n"); + + assert(qpdfjob_run_from_json("{\n\ + \"inputFile\": \"nothing-there.pdf\"\n\ +}") == 2); + printf("json error test passed\n"); +} + +int main(int argc, char* argv[]) +{ + if ((argc == 2) && (strcmp(argv[1], "wide") == 0)) + { +#ifndef QPDF_NO_WCHAR_T + wide_test(); +#else + printf("skipped wide\n"); +#endif // QPDF_NO_WCHAR_T + return 0; + } + + run_tests(); + return 0; +} diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 040567cf..dcc707a3 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -402,7 +402,7 @@ my @good_json = ( "underlay-overlay-password", "misc-options", ); -$n_tests += 4 + scalar(@bad_json) + (2 * scalar(@good_json)); +$n_tests += 10 + scalar(@bad_json) + (2 * scalar(@good_json)); foreach my $i (@bad_json) @@ -457,6 +457,34 @@ $td->runtest("json output from job", {$td->FILE => "job-json-output.out.json", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +$td->runtest("C job API", + {$td->COMMAND => "qpdfjob-ctest"}, + {$td->FILE => "qpdfjob-ctest.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +foreach my $i (['a.pdf', 1], ['b.pdf', 2], ['c.pdf', 3]) +{ + $td->runtest("check output", + {$td->FILE => $i->[0]}, + {$td->FILE => "qpdfjob-ctest$i->[1].pdf"}); +} +my $wide_out = `qpdfjob-ctest wide`; +$td->runtest("qpdfjob-ctest wide", + {$td->STRING => "$?: $wide_out"}, + {$td->REGEXP => "0: (wide test passed|skipped wide)\n"}, + $td->NORMALIZE_NEWLINES); +if ($wide_out =~ m/skipped/) +{ + $td->runtest("skipped wide", + {$td->STRING => "yes"}, + {$td->STRING => "yes"}); +} +else +{ + $td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "qpdfjob-ctest-wide.pdf"}); +} + show_ntests(); # ---------- $td->notify("--- Form Tests ---"); diff --git a/qpdf/qtest/qpdf/qpdfjob-ctest-wide.pdf b/qpdf/qtest/qpdf/qpdfjob-ctest-wide.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b8c692edc2cd541ccf0b772e56c68cf5495c8438 GIT binary patch literal 799 zcmah{OODe(5alDJ&=PkjB(ekS?)E2^A`50R)s7saRqa79yGQSH(*FGG<8MO22YvL4bi3dU z#6lWihz!9Szde=U?F%aky?`_&Qq5hUyRHfQdZ{c>@E+xJ0~6pCby|}*`5LObZm7yi zWeo(Ia!=$u7khezS$upV${Re_z)9+dmsu!9>OtfF^Y2C0o7m;8n=T{hO^zlScLU=Fy8r>8zi`cb(}3l zUZDGKrbDgWa#h2ey-FkumD?Idx+8K#X`6mZ_*EKb7o{}EHS2gZuh PRyKr;l6L#xSw{Wvq8! zhy@nl5;6p9{NV(_IuKf9Y5`%$P$nkOTvvnxwUinhu%4z#0TbXQby|@(`D&`UuBlR^ z)CIIQ<(^}bihZ@BEH*v9<3iAJfpQH*PJxxIAr@+x$E4FJjuhRL9}akBqA(;zuHq4D z@VCgO8KdW~PE6%W&NGw?{RUcX%9&;wb9!e&V-I7Ml%zuED300cW5KS5gKzKo*mhqv z`g<2&&(GG6@7=$>{@z$y{Rfvn#*NFL`Q58qH@@7wvnJQBnb-aqnN`(&QDE7Eb%fbF z)M+fhI+Rp0l?$+5r}Bv`(i(Unj%g+l89rLhbU7f?8U1e>EOXU? zrqv)Z~x0}b!Z2xIGu|$ zBlpcrhf0~{Dz|Zzo+1Qb^?NYs+Md%2Vno3c!TGF%v-X^eAg}{2{6zTS|0AYm4+<@E PUE1Ke4r?|KUqZ^4|D>$ol3WFSpVYkck_-hC zQ&Rb9TzX ztj7idZSQNl{@;-6GF6V;G)*XD_S>a8En6-e-o&1-)U=YJRNr7<;n0(l z`RD0rnYy;7HZuc-r5Bp!HnuAAwr@)<*)Z?H#Y|P%57(MKroD2|Ic?#g|MFZ@%f%vX z)|9n-P6RH05)`#(;xoQARzGhv&lJ6MY3&|?8x=cKHeB`NJ9;S6lBK$lGidd-m^*hC z-2BqMp<~See16MIi(@I*ZTNn(?oZWv`8;Qx-gUk3`Kx_Y^Fz$^X6r>8 zE_GVawBXPJriD%q;qYhizvHPK^S34QB*&zbrE2XzRQKZjvdJCl%S4r*Th{Jx+|K1!leuT&bR0YMZ(GwP$p!|E*b7m#$rzylvUMEv8?WtOZJ)-M;*7 z?EaQBne*DWZ-~iXzP--nbMYPD--$L0DkYEIE|lEIH?OZg`Iw5t+?g{K&+uECXZoe) zr9dMT$O0u2BQvDbf;CM*6OE}cN@^(P3cOHm9AaP+%HbJoV9un(+&V#omG}RmGhfc^ zIU>{AbmU72E3+m!3evJcnmP@Hlm@wjS+TR^B7{q6N_m%(Ip!>mLrNbB_p0VOv{Nb+Q_jSQMM^r^TcCVZbEd~Mvmo(!cEDA zCjrxP8WwH~wk&hJ@eA?6+n?4 zq@W*^pI-tL2eX4St5OwA%z+AA6rycX4UCNvEi97F6D=*wQ?+ocSsOPz*rXxEaBCrN0vAR=6_+Fyl~fd^rg0e? Om>F`Zs=E5SaRC66_qgBy literal 0 HcmV?d00001 diff --git a/qpdf/qtest/qpdf/qpdfjob-ctest3.pdf b/qpdf/qtest/qpdf/qpdfjob-ctest3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0031d7c4b49926f79bca021f6fd5617ac504f7fe GIT binary patch literal 16504 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�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 h$%Fde{>RzH(8