diff options
35 files changed, 465 insertions, 87 deletions
diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b81..55b97637 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,4 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +/inspectionProfiles diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9f47a0..31ee01ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.16) # the project line. When updating the version, check make_dist for all # the places it has to be updated. project(qpdf - VERSION 11.6.3 + VERSION 11.7.0 LANGUAGES C CXX) # Enable correct rpath handling for MacOSX @@ -1,3 +1,23 @@ +2023-12-16 Jay Berkenbilt <ejb@ql.org> + + * Add new C++ functions "qpdf_c_get_qpdf" and "qpdf_c_wrap" to + qpdf-c.h that make it possible to write your own extern "C" + functions in C++ that interoperate with the C API. See + examples/extend-c-api for more information. + +2023-12-10 Jay Berkenbilt <ejb@ql.org> + + * 11.6.4: release + +2023-12-09 Jay Berkenbilt <ejb@ql.org> + + * Install fix: include cmake files with the dev component. + +2023-11-20 Jay Berkenbilt <ejb@ql.org> + + * Build AppImage with an older Linux distribution to support AWS + Lambda. Fixes #1086. + 2023-10-15 Jay Berkenbilt <ejb@ql.org> * 11.6.3: release diff --git a/appimage/Dockerfile b/appimage/Dockerfile index c1914144..24c9c672 100644 --- a/appimage/Dockerfile +++ b/appimage/Dockerfile @@ -1,13 +1,19 @@ -FROM ubuntu:20.04 +FROM ubuntu:18.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get -y install screen git sudo \ - build-essential pkg-config cmake \ + build-essential pkg-config \ zlib1g-dev libjpeg-dev libgnutls28-dev \ python3-pip texlive-latex-extra latexmk \ inkscape imagemagick busybox-static wget fuse && \ apt-get clean && rm -rf /var/lib/apt/lists/* -RUN pip3 install sphinx sphinx_rtd_theme +# Get cmake from pypi. We need to keep Ubuntu 18.04 for a while longer +# since the glibc in Ubuntu 20.04 is too new (as of late 2023) for +# Amazon Linux 2 in Lambda and for some supported CentOS versions. +# When we are ready to update to 20.04 or newer, remove the version +# constraint on sphinx, and install the OS package for cmake. +RUN pip3 install --upgrade pip +RUN pip3 install sphinx==4 sphinx_rtd_theme cmake COPY entrypoint /entrypoint RUN chmod +x /entrypoint ENTRYPOINT [ "/entrypoint" ] diff --git a/appimage/build-appimage b/appimage/build-appimage index 0e86bff6..f970b7df 100755 --- a/appimage/build-appimage +++ b/appimage/build-appimage @@ -47,14 +47,14 @@ fi _osversion=$(cat /etc/os-release | grep PRETTY_NAME | awk -F'=' '{print $2}' | sed 's#"##g') # Warn users building the AppImage locally: -if [[ ! $_osversion =~ Ubuntu\ 20.04.*\ LTS ]]; then +if [[ ! $_osversion =~ Ubuntu\ 18.04.*\ LTS ]]; then set +x echo "" # 0 1 2 3 4 5 6 7 # 01234567890123456789012345678901234567890123456789012345678901234567890123456789 echo "+===========================================================================+" echo "|| WARNING: You are about to build a QPDF AppImage on a system which is ||" - echo "|| NOT Ubuntu 20.04 LTS. ||" + echo "|| NOT Ubuntu 18.04 LTS. ||" echo "|| ||" echo "|| It is recommended that you use a distribution that is at least a ||" echo "|| few years old to maximize the number of Linux distributions the ||" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9af85fe2..614047d5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -34,6 +34,11 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS}) endforeach() target_include_directories(pdf-create PRIVATE ${JPEG_INCLUDE}) +# extend-c-api contains a mixture of C and C++ files. +add_executable(extend-c-api extend-c-api-impl.cc extend-c-api.c) +set_property(TARGET extend-c-api PROPERTY LINKER_LANGUAGE CXX) +target_link_libraries(extend-c-api libqpdf) + add_test( NAME examples COMMAND ${RUN_QTEST} @@ -47,7 +52,7 @@ add_test( --tc "${qpdf_SOURCE_DIR}/examples/*.cc" --tc "${qpdf_SOURCE_DIR}/examples/*.c") -file(GLOB EXAMPLES_SRC "*.c" "*.cc") +file(GLOB EXAMPLES_SRC "*.c" "*.cc" "*.h") if(INSTALL_EXAMPLES) install(FILES ${EXAMPLES_SRC} DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples diff --git a/examples/extend-c-api-impl.cc b/examples/extend-c-api-impl.cc new file mode 100644 index 00000000..680d957b --- /dev/null +++ b/examples/extend-c-api-impl.cc @@ -0,0 +1,29 @@ +// This is an example of how to write C++ functions and make them usable with the qpdf C API. It +// consists of three files: +// - extend-c-api.h -- a plain C header file +// - extend-c-api.c -- a C program that calls the function +// - extend-c-api.cc -- a C++ file that implements the function + +#include "extend-c-api.h" + +// Here, we add a function to get the number of pages in a PDF file and make it callable through the +// C API. + +// This is a normal C++ function that works with QPDF in a normal way. It doesn't do anything +// special to be callable from C. +int +numPages(std::shared_ptr<QPDF> qpdf) +{ + return qpdf->getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt(); +} + +// Now we define the glue that makes our function callable using the C API. + +// This is the C++ implementation of the C function. +QPDF_ERROR_CODE +num_pages(qpdf_data qc, int* npages) +{ + // Call qpdf_c_wrap to convert any exception our function might through to a QPDF_ERROR_CODE + // and attach it to the qpdf_data object in the same way as other functions in the C API. + return qpdf_c_wrap(qc, [&qc, &npages]() { *npages = numPages(qpdf_c_get_qpdf(qc)); }); +} diff --git a/examples/extend-c-api.c b/examples/extend-c-api.c new file mode 100644 index 00000000..aa603845 --- /dev/null +++ b/examples/extend-c-api.c @@ -0,0 +1,67 @@ +/* + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It + * consists of three files: + * - extend-c-api.h -- a plain C header file + * - extend-c-api.c -- a C program that calls the function + * - extend-c-api.cc -- a C++ file that implements the function + */ + +#include "extend-c-api.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static char const* whoami = 0; + +static void +usage() +{ + fprintf(stderr, "Usage: %s infile\n", whoami); + exit(2); +} + +int +main(int argc, char* argv[]) +{ + char* infile = NULL; + qpdf_data qpdf = qpdf_init(); + int warnings = 0; + int errors = 0; + char* p = NULL; + + if ((p = strrchr(argv[0], '/')) != NULL) { + whoami = p + 1; + } else if ((p = strrchr(argv[0], '\\')) != NULL) { + whoami = p + 1; + } else { + whoami = argv[0]; + } + + if (argc != 2) { + usage(); + } + + infile = argv[1]; + + if ((qpdf_read(qpdf, infile, NULL) & QPDF_ERRORS) == 0) { + int npages; + if ((num_pages(qpdf, &npages) & QPDF_ERRORS) == 0) { + printf("num pages = %d\n", npages); + } + } + if (qpdf_more_warnings(qpdf)) { + warnings = 1; + } + if (qpdf_has_error(qpdf)) { + errors = 1; + printf("error: %s\n", qpdf_get_error_full_text(qpdf, qpdf_get_error(qpdf))); + } + qpdf_cleanup(&qpdf); + if (errors) { + return 2; + } else if (warnings) { + return 3; + } + + return 0; +} diff --git a/examples/extend-c-api.h b/examples/extend-c-api.h new file mode 100644 index 00000000..3b2d12d4 --- /dev/null +++ b/examples/extend-c-api.h @@ -0,0 +1,25 @@ +#ifndef EXAMPLE_C_EXTEND_H +#define EXAMPLE_C_EXTEND_H + +/* + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It + * consists of three files: + * - extend-c-api.h -- a plain C header file + * - extend-c-api.c -- a C program that calls the function + * - extend-c-api.cc -- a C++ file that implements the function + */ +#include <qpdf/qpdf-c.h> + +/* Declare your custom function to return QPDF_ERROR_CODE and take qpdf_data and anything else you + * need. Any errors are retrievable through the qpdf C APIs normal error-handling mechanism. + */ + +#ifdef __cplusplus +extern "C" { +#endif + QPDF_ERROR_CODE num_pages(qpdf_data qc, int* npages); +#ifdef __cplusplus +} +#endif + +#endif /* EXAMPLE_C_EXTEND_H */ diff --git a/examples/qtest/extend-c-api.test b/examples/qtest/extend-c-api.test new file mode 100644 index 00000000..3dd82193 --- /dev/null +++ b/examples/qtest/extend-c-api.test @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +require 5.008; +use warnings; +use strict; + +chdir("extend-c-api") or die "chdir testdir failed: $!\n"; + +require TestDriver; + +cleanup(); + +my $td = new TestDriver('extend-c-api'); + +$td->runtest("extend C API (good)", + {$td->COMMAND => "extend-c-api good.pdf"}, + {$td->FILE => "good.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("extend C API (bad)", + {$td->COMMAND => "extend-c-api bad.pdf"}, + {$td->FILE => "bad.out", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); + +cleanup(); + +$td->report(2); + +sub cleanup +{ + unlink "a.pdf"; +} diff --git a/examples/qtest/extend-c-api/bad.out b/examples/qtest/extend-c-api/bad.out new file mode 100644 index 00000000..51f1d56b --- /dev/null +++ b/examples/qtest/extend-c-api/bad.out @@ -0,0 +1,5 @@ +WARNING: bad.pdf: can't find PDF header +WARNING: bad.pdf: file is damaged +WARNING: bad.pdf: can't find startxref +WARNING: bad.pdf: Attempting to reconstruct cross-reference table +error: bad.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/examples/qtest/extend-c-api/bad.pdf b/examples/qtest/extend-c-api/bad.pdf new file mode 100644 index 00000000..a3d2d925 --- /dev/null +++ b/examples/qtest/extend-c-api/bad.pdf @@ -0,0 +1 @@ +not even a pdf file diff --git a/examples/qtest/extend-c-api/good.out b/examples/qtest/extend-c-api/good.out new file mode 100644 index 00000000..98205fae --- /dev/null +++ b/examples/qtest/extend-c-api/good.out @@ -0,0 +1 @@ +num pages = 1 diff --git a/examples/qtest/extend-c-api/good.pdf b/examples/qtest/extend-c-api/good.pdf new file mode 100644 index 00000000..42867b96 --- /dev/null +++ b/examples/qtest/extend-c-api/good.pdf @@ -0,0 +1,64 @@ +%PDF-2.0 +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj +2 0 obj +<< + /Count 1 + /Kids [ + 3 0 R + ] + /Type /Pages +>> +endobj +3 0 obj +<< + /Contents 4 0 R + /MediaBox [ 0 0 612 792 ] + /Parent 2 0 R + /Resources << + /Font << /F1 5 0 R >> + >> + /Type /Page +>> +endobj +4 0 obj +<< + /Length 44 +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj +5 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Subtype /Type1 + /Type /Font +>> +endobj + +xref +0 6 +0000000000 65535 f +0000000009 00000 n +0000000062 00000 n +0000000133 00000 n +0000000277 00000 n +0000000372 00000 n +trailer << + /Root 1 0 R + /Size 6 + /ID [<42841c13bbf709d79a200fa1691836f8><b1d8b5838eeafe16125317aa78e666aa>] +>> +startxref +478 +%%EOF diff --git a/include/qpdf/Buffer.hh b/include/qpdf/Buffer.hh index c0669c6c..d0e6eda0 100644 --- a/include/qpdf/Buffer.hh +++ b/include/qpdf/Buffer.hh @@ -24,6 +24,7 @@ #include <cstddef> #include <memory> +#include <string> class Buffer { @@ -35,11 +36,15 @@ class Buffer // object is destroyed. QPDF_DLL Buffer(size_t size); + QPDF_DLL + Buffer(std::string&& content); // Create a Buffer object whose memory is owned by the caller and will not be freed when the // Buffer is destroyed. QPDF_DLL Buffer(unsigned char* buf, size_t size); + QPDF_DLL + Buffer(std::string& content); [[deprecated("Move Buffer or use Buffer::copy instead")]] QPDF_DLL Buffer(Buffer const&); [[deprecated("Move Buffer or use Buffer::copy instead")]] QPDF_DLL Buffer& @@ -75,8 +80,10 @@ class Buffer private: Members(size_t size, unsigned char* buf, bool own_memory); + Members(std::string&& content); Members(Members const&) = delete; + std::string str; bool own_memory; size_t size; unsigned char* buf; diff --git a/include/qpdf/DLL.h b/include/qpdf/DLL.h index 74c3c62e..6dd5137e 100644 --- a/include/qpdf/DLL.h +++ b/include/qpdf/DLL.h @@ -25,9 +25,9 @@ /* The first version of qpdf to include the version constants is 10.6.0. */ #define QPDF_MAJOR_VERSION 11 -#define QPDF_MINOR_VERSION 6 -#define QPDF_PATCH_VERSION 3 -#define QPDF_VERSION "11.6.3" +#define QPDF_MINOR_VERSION 7 +#define QPDF_PATCH_VERSION 0 +#define QPDF_VERSION "11.7.0" /* * This file defines symbols that control the which functions, diff --git a/include/qpdf/Pl_Buffer.hh b/include/qpdf/Pl_Buffer.hh index 5030e10e..5def8b4f 100644 --- a/include/qpdf/Pl_Buffer.hh +++ b/include/qpdf/Pl_Buffer.hh @@ -33,7 +33,7 @@ #include <qpdf/PointerHolder.hh> // unused -- remove in qpdf 12 (see #785) #include <memory> -#include <vector> +#include <string> class QPDF_DLL_CLASS Pl_Buffer: public Pipeline { @@ -64,6 +64,10 @@ class QPDF_DLL_CLASS Pl_Buffer: public Pipeline QPDF_DLL void getMallocBuffer(unsigned char** buf, size_t* len); + // Same as getBuffer but returns the result as a string. + QPDF_DLL + std::string getString(); + private: class QPDF_DLL_PRIVATE Members { @@ -78,7 +82,7 @@ class QPDF_DLL_CLASS Pl_Buffer: public Pipeline Members(Members const&) = delete; bool ready{true}; - std::vector<unsigned char> data; + std::string data; }; std::shared_ptr<Members> m; diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 2ca6005c..ffe07559 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -1133,7 +1133,7 @@ class QPDF Pipeline*& pipeline, QPDFObjGen const& og, QPDFObjectHandle& stream_dict, - std::vector<std::shared_ptr<Pipeline>>& heap); + std::unique_ptr<Pipeline>& heap); // Methods to support object copying void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top); diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index d4305602..f989ba15 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -21,21 +21,23 @@ #define QPDF_C_H /* - * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's + * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's * capabilities to make them accessible to callers who can't handle calling C++ functions or working - * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL + * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL * directly or to other people programming in non-C/C++ languages that can call C code but not C++ - * code. + * code. Starting with qpdf 11.7, it is possible to write your own `extern "C"` functions that + * interoperate with the C API. * * There are several things to keep in mind when using the C API. * * Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR * HANDLING" below for a detailed explanation. * - * The C API is not as rich as the C++ API. For any operations that involve actually - * manipulating PDF objects, you must use the C++ API. The C API is primarily useful for doing - * basic transformations on PDF files similar to what you might do with the qpdf command-line - * tool. + * The C API is not as rich as the C++ API. For many operations, you must use the C++ API. The C + * API is primarily useful for doing basic transformations on PDF files similar to what you + * might do with the qpdf command-line tool. You can write your own `extern "C"` functions in + * C++ that interoperate with the C API by using qpdf_c_get_qpdf and qpdf_c_wrap which were + * introduced in qpdf 11.7.0. * * These functions store their state in a qpdf_data object. Individual instances of qpdf_data * are not thread-safe: although you may access different qpdf_data objects from different @@ -990,6 +992,23 @@ extern "C" { QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page); #ifdef __cplusplus } + +// These C++ functions make it easier to write C++ code that interoperates with the C API. +// See examples/extend-c-api. + +# include <functional> +# include <memory> + +# include <qpdf/QPDF.hh> + +// Retrieve the real QPDF object attached to this qpdf_data. +QPDF_DLL +std::shared_ptr<QPDF> qpdf_c_get_qpdf(qpdf_data qpdf); + +// Wrap a C++ function that may throw an exception to translate the exception for retrieval using +// the normal QPDF C API methods. +QPDF_DLL +QPDF_ERROR_CODE qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn); #endif #endif /* QPDF_C_H */ @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431 libqpdf/qpdf/auto_job_json_init.hh 85ac7e5c66f14c767419823eac84bdea4bd72d690bfe12b533321e5708e644b7 libqpdf/qpdf/auto_job_schema.hh 5e0f5cb7d462716fe52548b2ae1a8eb6f3c900016e915140eea37f78cee45b2b manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 -manual/cli.rst 35d32800cee1871e74c85419c481cae1c544c8b0eb462df82cf373a32619ab73 +manual/cli.rst 4f2806f7cf77f167fd41b065a8916f02fdfaab35ad1a74587578bf2954228464 diff --git a/libqpdf/Buffer.cc b/libqpdf/Buffer.cc index 3dddd5db..20453a40 100644 --- a/libqpdf/Buffer.cc +++ b/libqpdf/Buffer.cc @@ -27,6 +27,14 @@ Buffer::Members::Members(size_t size, unsigned char* buf, bool own_memory) : } } +Buffer::Members::Members(std::string&& content) : + str(std::move(content)), + own_memory(false), + size(str.size()), + buf(reinterpret_cast<unsigned char*>(str.data())) +{ +} + Buffer::Members::~Members() { if (this->own_memory) { @@ -44,11 +52,21 @@ Buffer::Buffer(size_t size) : { } +Buffer::Buffer(std::string&& content) : + m(new Members(std::move(content))) +{ +} + Buffer::Buffer(unsigned char* buf, size_t size) : m(new Members(size, buf, false)) { } +Buffer::Buffer(std::string& content) : + m(new Members(content.size(), reinterpret_cast<unsigned char*>(content.data()), false)) +{ +} + Buffer::Buffer(Buffer const& rhs) { assert(test_mode); diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index c6a122c6..eb30b622 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -590,10 +590,12 @@ if(INSTALL_CMAKE_PACKAGE) install(EXPORT libqpdfTargets NAMESPACE qpdf:: FILE libqpdfTargets.cmake + COMPONENT ${COMPONENT_DEV} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfig.cmake + COMPONENT ${COMPONENT_DEV} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf) endif() diff --git a/libqpdf/Pl_Buffer.cc b/libqpdf/Pl_Buffer.cc index 766c04b5..9994bedd 100644 --- a/libqpdf/Pl_Buffer.cc +++ b/libqpdf/Pl_Buffer.cc @@ -19,7 +19,7 @@ Pl_Buffer::~Pl_Buffer() // NOLINT (modernize-use-equals-default) void Pl_Buffer::write(unsigned char const* buf, size_t len) { - m->data.insert(m->data.end(), buf, buf + len); + m->data.append(reinterpret_cast<char const*>(buf), len); m->ready = false; if (getNext(true)) { @@ -42,15 +42,20 @@ Pl_Buffer::getBuffer() if (!m->ready) { throw std::logic_error("Pl_Buffer::getBuffer() called when not ready"); } + auto* b = new Buffer(std::move(m->data)); + m->data.clear(); + return b; +} - auto size = m->data.size(); - auto* b = new Buffer(size); - if (size > 0) { - unsigned char* p = b->getBuffer(); - memcpy(p, m->data.data(), size); +std::string +Pl_Buffer::getString() +{ + if (!m->ready) { + throw std::logic_error("Pl_Buffer::getString() called when not ready"); } + auto s = std::move(m->data); m->data.clear(); - return b; + return s; } std::shared_ptr<Buffer> diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 473cf5f0..f6badc35 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -2413,29 +2413,23 @@ QPDF::pipeStreamData( bool suppress_warnings, bool will_retry) { - std::vector<std::shared_ptr<Pipeline>> to_delete; + std::unique_ptr<Pipeline> to_delete; if (encp->encrypted) { decryptStream(encp, file, qpdf_for_warning, pipeline, og, stream_dict, to_delete); } bool attempted_finish = false; - bool success = false; try { file->seek(offset, SEEK_SET); - char buf[10240]; - while (length > 0) { - size_t to_read = (sizeof(buf) < length ? sizeof(buf) : length); - size_t len = file->read(buf, to_read); - if (len == 0) { - throw damagedPDF( - file, "", file->getLastOffset(), "unexpected EOF reading stream data"); - } - length -= len; - pipeline->write(buf, len); + auto buf = std::make_unique<char[]>(length); + if (auto read = file->read(buf.get(), length); read != length) { + throw damagedPDF( + file, "", offset + toO(read), "unexpected EOF reading stream data"); } + pipeline->write(buf.get(), length); attempted_finish = true; pipeline->finish(); - success = true; + return true; } catch (QPDFExc& e) { if (!suppress_warnings) { qpdf_for_warning.warn(e); @@ -2458,8 +2452,7 @@ QPDF::pipeStreamData( file, "", file->getLastOffset(), - "stream will be re-processed without" - " filtering to avoid data loss")); + "stream will be re-processed without filtering to avoid data loss")); } } } @@ -2470,7 +2463,7 @@ QPDF::pipeStreamData( // ignore } } - return success; + return false ; } bool diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 5a54bf75..f2daa0c7 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -584,8 +584,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( ResourceReplacer rr(dr_map, rf.getNamesByResourceType()); Pl_Buffer buf_pl("filtered DA"); da_stream.filterAsContents(&rr, &buf_pl); - auto buf = buf_pl.getBufferSharedPointer(); - std::string new_da(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize()); + std::string new_da = buf_pl.getString(); obj.replaceKey("/DA", QPDFObjectHandle::newString(new_da)); } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index d5ac137a..d543f98e 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1708,8 +1708,7 @@ QPDFObjectHandle::pipeContentStreams( need_newline = (lc.getLastChar() != static_cast<unsigned char>('\n')); QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1); } - std::unique_ptr<Buffer> b(buf.getBuffer()); - p->write(b->getBuffer(), b->getSize()); + p->writeString(buf.getString()); p->finish(); } diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index fd6e5215..54bb5cac 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -175,14 +175,11 @@ InlineImageTracker::handleToken(QPDFTokenizer::Token const& token) size_t len = image_data.length(); if (len >= this->min_size) { QTC::TC("qpdf", "QPDFPageObjectHelper externalize inline image"); - Pl_Buffer b("image_data"); - b.writeString(image_data); - b.finish(); QPDFObjectHandle dict = convertIIDict(QPDFObjectHandle::parse(dict_str)); dict.replaceKey("/Length", QPDFObjectHandle::newInteger(QIntC::to_longlong(len))); std::string name = resources.getUniqueResourceName("/IIm", this->min_suffix); QPDFObjectHandle image = - QPDFObjectHandle::newStream(this->qpdf, b.getBufferSharedPointer()); + QPDFObjectHandle::newStream(this->qpdf, std::make_shared<Buffer>(std::move(image_data))); image.replaceDict(dict); resources.getKey("/XObject").replaceKey(name, image); write(name); diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index db3b42e0..664ea5ff 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -1579,10 +1579,7 @@ QPDFWriter::unparseObject( m->cur_data_key.length()); pl.writeString(val); pl.finish(); - auto buf = bufpl.getBufferSharedPointer(); - val = QPDF_String( - std::string(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize())) - .unparse(true); + val = QPDF_String(bufpl.getString()).unparse(true); } else { auto tmp_ph = QUtil::make_unique_cstr(val); char* tmp = tmp_ph.get(); diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 45d6fb70..a43d91ff 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -216,29 +216,28 @@ QPDF_Stream::getStreamJSON( auto dict = this->stream_dict; JSON result = JSON::makeDictionary(); if (json_data != qpdf_sj_none) { - std::shared_ptr<Buffer> buf; + Pl_Discard discard; + Pl_Buffer buf_pl{"stream data"}; + // buf_pl contains valid data and is ready for retrieval of the data. + bool buf_pl_ready = false; bool filtered = false; bool filter = (decode_level != qpdf_dl_none); for (int attempt = 1; attempt <= 2; ++attempt) { - Pl_Discard discard; - std::shared_ptr<Pl_Buffer> buf_pl; - Pipeline* data_pipeline = nullptr; + Pipeline* data_pipeline = &discard; if (json_data == qpdf_sj_file) { // We need to capture the data to write - buf_pl = std::make_shared<Pl_Buffer>("stream data"); - data_pipeline = buf_pl.get(); - } else { - data_pipeline = &discard; + data_pipeline = &buf_pl; } bool succeeded = pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1)); - if ((!succeeded) || (filter && (!filtered))) { + if (!succeeded || (filter && !filtered)) { // Try again filter = false; decode_level = qpdf_dl_none; + buf_pl.getString(); // reset buf_pl } else { - if (buf_pl.get()) { - buf = buf_pl->getBufferSharedPointer(); + if (json_data == qpdf_sj_file) { + buf_pl_ready = true; } break; } @@ -252,10 +251,10 @@ QPDF_Stream::getStreamJSON( } if (json_data == qpdf_sj_file) { result.addDictionaryMember("datafile", JSON::makeString(data_filename)); - if (!buf.get()) { + if (!buf_pl_ready) { throw std::logic_error("QPDF_Stream: failed to get stream data in json file mode"); } - p->write(buf->getBuffer(), buf->getSize()); + p->writeString(buf_pl.getString()); } else if (json_data == qpdf_sj_inline) { result.addDictionaryMember( "data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index b5cc44ee..1dd7b963 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -229,13 +229,11 @@ process_with_aes( aes.writeString(data); } aes.finish(); - auto bufp = buffer.getBufferSharedPointer(); if (outlength == 0) { - outlength = bufp->getSize(); + return buffer.getString(); } else { - outlength = std::min(outlength, bufp->getSize()); + return buffer.getString().substr(0, outlength); } - return {reinterpret_cast<char*>(bufp->getBuffer()), outlength}; } static std::string @@ -1021,8 +1019,7 @@ QPDF::decryptString(std::string& str, QPDFObjGen const& og) key.length()); pl.writeString(str); pl.finish(); - auto buf = bufpl.getBufferSharedPointer(); - str = std::string(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize()); + str = bufpl.getString(); } else { QTC::TC("qpdf", "QPDF_encryption rc4 decode string"); size_t vlen = str.length(); @@ -1041,6 +1038,9 @@ QPDF::decryptString(std::string& str, QPDFObjGen const& og) } } +// Prepend a decryption pipeline to 'pipeline'. The decryption pipeline (returned as +// 'decrypt_pipeline' must be owned by the caller to ensure that it stays alive while the pipeline +// is in use. void QPDF::decryptStream( std::shared_ptr<EncryptionParameters> encp, @@ -1049,7 +1049,7 @@ QPDF::decryptStream( Pipeline*& pipeline, QPDFObjGen const& og, QPDFObjectHandle& stream_dict, - std::vector<std::shared_ptr<Pipeline>>& heap) + std::unique_ptr<Pipeline>& decrypt_pipeline) { std::string type; if (stream_dict.getKey("/Type").isName()) { @@ -1085,8 +1085,7 @@ QPDF::decryptStream( crypt_params.getKey("/Name").isName()) { QTC::TC("qpdf", "QPDF_encrypt crypt array"); method = interpretCF(encp, crypt_params.getKey("/Name")); - method_source = "stream's Crypt " - "decode parameters (array)"; + method_source = "stream's Crypt decode parameters (array)"; } } } @@ -1135,10 +1134,9 @@ QPDF::decryptStream( } } std::string key = getKeyForObject(encp, og, use_aes); - std::shared_ptr<Pipeline> new_pipeline; if (use_aes) { QTC::TC("qpdf", "QPDF_encryption aes decode stream"); - new_pipeline = std::make_shared<Pl_AES_PDF>( + decrypt_pipeline = std::make_unique<Pl_AES_PDF>( "AES stream decryption", pipeline, false, @@ -1146,14 +1144,13 @@ QPDF::decryptStream( key.length()); } else { QTC::TC("qpdf", "QPDF_encryption rc4 decode stream"); - new_pipeline = std::make_shared<Pl_RC4>( + decrypt_pipeline = std::make_unique<Pl_RC4>( "RC4 stream decryption", pipeline, QUtil::unsigned_char_pointer(key), toI(key.length())); } - pipeline = new_pipeline.get(); - heap.push_back(new_pipeline); + pipeline = decrypt_pipeline.get(); } void diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index 1f7b85cb..b45b564b 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -1949,3 +1949,15 @@ qpdf_write_json( }); return status; } + +std::shared_ptr<QPDF> +qpdf_c_get_qpdf(qpdf_data qpdf) +{ + return qpdf->qpdf; +} + +QPDF_ERROR_CODE +qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn) +{ + return trap_errors(qpdf, [&fn](qpdf_data) { fn(); }); +} diff --git a/libtests/buffer.cc b/libtests/buffer.cc index e62a37ca..1b87bb7d 100644 --- a/libtests/buffer.cc +++ b/libtests/buffer.cc @@ -35,6 +35,21 @@ main() assert(bc2p != bc1p); assert(bc2p[0] == 'R'); assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&&) + Buffer bc3("QW"); + unsigned char* bc3p = bc3.getBuffer(); + Buffer bc4(bc3.copy()); + bc3p[0] = 'R'; + unsigned char* bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'Q'); + assert(bc4p[1] == 'W'); + bc4 = bc3.copy(); + bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'R'); + assert(bc4p[1] == 'W'); } #ifdef _MSC_VER @@ -62,6 +77,37 @@ main() assert(bc2p != bc1p); assert(bc2p[0] == 'R'); assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&&) + Buffer bc3("QW"); + unsigned char* bc3p = bc3.getBuffer(); + Buffer bc4(bc3); + bc3p[0] = 'R'; + unsigned char* bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'Q'); + assert(bc4p[1] == 'W'); + bc4 = bc3; + bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc2p[0] == 'R'); + assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&) + std::string s{"QW"}; + Buffer bc5(s); + unsigned char* bc5p = bc5.getBuffer(); + Buffer bc6(bc5); + bc5p[0] = 'R'; + unsigned char* bc6p = bc6.getBuffer(); + assert(bc6p != bc5p); + assert(bc6p[0] == 'Q'); + assert(bc6p[1] == 'W'); + bc6 = bc5; + bc6p = bc6.getBuffer(); + assert(bc6p != bc5p); + assert(bc2p[0] == 'R'); + assert(bc2p[1] == 'W'); } #if (defined(__GNUC__) || defined(__clang__)) # pragma GCC diagnostic pop @@ -82,6 +128,19 @@ main() Buffer bm3 = std::move(bm2); unsigned char* bm3p = bm3.getBuffer(); assert(bm3p == bm2p); + + // Test Buffer(dtd::string&&) + Buffer bm4("QW"); + unsigned char* bm4p = bm4.getBuffer(); + Buffer bm5(std::move(bm4)); + bm4p[0] = 'R'; + unsigned char* bm5p = bm5.getBuffer(); + assert(bm5p == bm4p); + assert(bm5p[0] == 'R'); + + Buffer bm6 = std::move(bm5); + unsigned char* bm6p = bm6.getBuffer(); + assert(bm6p == bm5p); } try { diff --git a/manual/cli.rst b/manual/cli.rst index 4e01b65f..482d65f9 100644 --- a/manual/cli.rst +++ b/manual/cli.rst @@ -749,7 +749,7 @@ Related Options signature but leaves its visual appearance intact. Remove security restrictions associated with digitally signed PDF - files. This may be combined with :qpdf:option:--decrypt: to allow + files. This may be combined with :qpdf:ref:--decrypt: to allow free editing of previously signed/encrypted files. This option invalidates the signature but leaves its visual appearance intact. @@ -2176,7 +2176,7 @@ Related Options created in this way are insecure since they can be opened without a password, and restrictions will not be enforced. Users would ordinarily never want to create such files. If you are using qpdf - to intentionally created strange files for testing (a valid use of + to intentionally create strange files for testing (a valid use of qpdf!), this option allows you to create such insecure files. This option is only available with 256-bit encryption. diff --git a/manual/conf.py b/manual/conf.py index f0fd45c5..b95eb3c6 100644 --- a/manual/conf.py +++ b/manual/conf.py @@ -16,7 +16,7 @@ project = 'QPDF' copyright = '2005-2023, Jay Berkenbilt' author = 'Jay Berkenbilt' # make_dist and the CI build lexically find the release version from this file. -release = '11.6.3' +release = '11.7.0' version = release extensions = [ 'sphinx_rtd_theme', diff --git a/manual/release-notes.rst b/manual/release-notes.rst index a03ce499..7388f17d 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -38,6 +38,23 @@ Planned changes for future 12.x (subject to change): .. x.y.z: not yet released +11.7.0: not yet released + - Library Enhancements: + + - Add C++ functions ``qpdf_c_wrap`` and ``qpdf_c_get_qpdf`` to the + C API to enable custom C++ code to interoperate more easily with + the the C API. See ``examples/extend-c-api``. + +11.6.4: December 10, 2023 + - Bug fixes: + + - When running ``cmake --install --component dev``, install cmake + files, which were previously omitted from the ``dev`` component + + - Fix the Linux binary build to use older libraries so it + continues to work in AWS Lambda and other older execution + environments. + 11.6.3: October 15, 2023 - Bug fixes: @@ -48,7 +65,7 @@ Planned changes for future 12.x (subject to change): content streams with default settings. - The linearization specification precludes linearized files that - require offets past the 4 GB mark. A bug in qpdf was preventing + require offsets past the 4 GB mark. A bug in qpdf was preventing it from working when offsets had to pass the 2 GB mark. This has been corrected. |