aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.idea/.gitignore1
-rw-r--r--CMakeLists.txt2
-rw-r--r--ChangeLog20
-rw-r--r--appimage/Dockerfile12
-rwxr-xr-xappimage/build-appimage4
-rw-r--r--examples/CMakeLists.txt7
-rw-r--r--examples/extend-c-api-impl.cc29
-rw-r--r--examples/extend-c-api.c67
-rw-r--r--examples/extend-c-api.h25
-rw-r--r--examples/qtest/extend-c-api.test30
-rw-r--r--examples/qtest/extend-c-api/bad.out5
-rw-r--r--examples/qtest/extend-c-api/bad.pdf1
-rw-r--r--examples/qtest/extend-c-api/good.out1
-rw-r--r--examples/qtest/extend-c-api/good.pdf64
-rw-r--r--include/qpdf/Buffer.hh7
-rw-r--r--include/qpdf/DLL.h6
-rw-r--r--include/qpdf/Pl_Buffer.hh8
-rw-r--r--include/qpdf/QPDF.hh2
-rw-r--r--include/qpdf/qpdf-c.h33
-rw-r--r--job.sums2
-rw-r--r--libqpdf/Buffer.cc18
-rw-r--r--libqpdf/CMakeLists.txt2
-rw-r--r--libqpdf/Pl_Buffer.cc19
-rw-r--r--libqpdf/QPDF.cc25
-rw-r--r--libqpdf/QPDFAcroFormDocumentHelper.cc3
-rw-r--r--libqpdf/QPDFObjectHandle.cc3
-rw-r--r--libqpdf/QPDFPageObjectHelper.cc5
-rw-r--r--libqpdf/QPDFWriter.cc5
-rw-r--r--libqpdf/QPDF_Stream.cc25
-rw-r--r--libqpdf/QPDF_encryption.cc25
-rw-r--r--libqpdf/qpdf-c.cc12
-rw-r--r--libtests/buffer.cc59
-rw-r--r--manual/cli.rst4
-rw-r--r--manual/conf.py2
-rw-r--r--manual/release-notes.rst19
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
diff --git a/ChangeLog b/ChangeLog
index 696a3567..19be25a3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 */
diff --git a/job.sums b/job.sums
index 1b718776..8ab0d576 100644
--- a/job.sums
+++ b/job.sums
@@ -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.