diff --git a/CMakeLists.txt b/CMakeLists.txt index afca825..5ee99d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.16) # also find the version number here. generate_auto_job also reads the # version from here. project(qpdf - VERSION 12.0.0 + VERSION 12.0.1 LANGUAGES C CXX) # Enable correct rpath handling for MacOSX diff --git a/include/qpdf/DLL.h b/include/qpdf/DLL.h index d575784..4691776 100644 --- a/include/qpdf/DLL.h +++ b/include/qpdf/DLL.h @@ -27,12 +27,12 @@ /* The first version of qpdf to include the version constants is 10.6.0. */ #define QPDF_MAJOR_VERSION 12 #define QPDF_MINOR_VERSION 0 -#define QPDF_PATCH_VERSION 0 +#define QPDF_PATCH_VERSION 1 #ifdef QPDF_FUTURE -# define QPDF_VERSION "12.0.0+future" +# define QPDF_VERSION "12.0.1+future" #else -# define QPDF_VERSION "12.0.0" +# define QPDF_VERSION "12.0.1" #endif /* diff --git a/include/qpdf/InputSource.hh b/include/qpdf/InputSource.hh index 600a34c..0ece2bb 100644 --- a/include/qpdf/InputSource.hh +++ b/include/qpdf/InputSource.hh @@ -75,7 +75,11 @@ class QPDF_DLL_CLASS InputSource // semantically equivalent to seek(-1, SEEK_CUR) but is much more efficient. virtual void unreadCh(char ch) = 0; - // The following methods are for use by QPDFTokenizer + // The following methods are for internal use by qpdf only. + inline size_t read(std::string& str, size_t count, qpdf_offset_t at = -1); + inline std::string read(size_t count, qpdf_offset_t at = -1); + size_t read_line(std::string& str, size_t count, qpdf_offset_t at = -1); + std::string read_line(size_t count, qpdf_offset_t at = -1); inline qpdf_offset_t fastTell(); inline bool fastRead(char&); inline void fastUnread(bool); @@ -93,57 +97,4 @@ class QPDF_DLL_CLASS InputSource qpdf_offset_t buf_start = 0; }; -inline void -InputSource::loadBuffer() -{ - this->buf_idx = 0; - this->buf_len = qpdf_offset_t(read(this->buffer, this->buf_size)); - // NB read sets last_offset - this->buf_start = this->last_offset; -} - -inline qpdf_offset_t -InputSource::fastTell() -{ - if (this->buf_len == 0) { - loadBuffer(); - } else { - auto curr = tell(); - if (curr < this->buf_start || curr >= (this->buf_start + this->buf_len)) { - loadBuffer(); - } else { - this->last_offset = curr; - this->buf_idx = curr - this->buf_start; - } - } - return this->last_offset; -} - -inline bool -InputSource::fastRead(char& ch) -{ - // Before calling fastRead, fastTell must be called to prepare the buffer. Once reading is - // complete, fastUnread must be called to set the correct file position. - if (this->buf_idx < this->buf_len) { - ch = this->buffer[this->buf_idx]; - ++(this->buf_idx); - ++(this->last_offset); - return true; - - } else if (this->buf_len == 0) { - return false; - } else { - seek(this->buf_start + this->buf_len, SEEK_SET); - fastTell(); - return fastRead(ch); - } -} - -inline void -InputSource::fastUnread(bool back) -{ - this->last_offset -= back ? 1 : 0; - seek(this->last_offset, SEEK_SET); -} - #endif // QPDF_INPUTSOURCE_HH diff --git a/job.sums b/job.sums index 10e39f1..6121c93 100644 --- a/job.sums +++ b/job.sums @@ -1,5 +1,5 @@ # Generated by generate_auto_job -CMakeLists.txt 88e8974a8b14e10c941a4bb04ff078c3d3063b98af3ea056e02b1dcdff783d22 +CMakeLists.txt 7779469688d17b58dfe69f2af4e5627eb20defd72a95ca71f2ecee68f1ec6d97 generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 @@ -16,5 +16,5 @@ libqpdf/qpdf/auto_job_json_init.hh 344c2fb473f88fe829c93b1efe6c70a0e4796537b8eb3 libqpdf/qpdf/auto_job_schema.hh 6d3eef5137b8828eaa301a1b3cf75cb7bb812aa6e2d8301de865b42d238d7a7c manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 manual/cli.rst 67357688f9a52fafa9a4f231fe4ce74c3cd8977130da7501efe54439a1ee22d4 -manual/qpdf.1 78bad33f9b3f246f1800bce365f7be06d3545d89f08b8923dd8489031b5af43e +manual/qpdf.1 dbcc567623f1fa080743ae9bc32b6264a3b6bd3074c81c438e52ca328e94ecd7 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b diff --git a/libqpdf/InputSource.cc b/libqpdf/InputSource.cc index 405494e..1530ed4 100644 --- a/libqpdf/InputSource.cc +++ b/libqpdf/InputSource.cc @@ -1,10 +1,12 @@ -#include +#include #include #include #include #include +using namespace std::literals; + void InputSource::setLastOffset(qpdf_offset_t offset) { @@ -17,27 +19,42 @@ InputSource::getLastOffset() const return this->last_offset; } -std::string -InputSource::readLine(size_t max_line_length) +size_t +InputSource::read_line(std::string& str, size_t count, qpdf_offset_t at) { // Return at most max_line_length characters from the next line. Lines are terminated by one or // more \r or \n characters. Consume the trailing newline characters but don't return them. // After this is called, the file will be positioned after a line terminator or at the end of // the file, and last_offset will point to position the file had when this method was called. - qpdf_offset_t offset = this->tell(); - auto bp = std::make_unique(max_line_length + 1); - char* buf = bp.get(); - memset(buf, '\0', max_line_length + 1); - this->read(buf, max_line_length); - this->seek(offset, SEEK_SET); - qpdf_offset_t eol = this->findAndSkipNextEOL(); - this->last_offset = offset; - size_t line_length = QIntC::to_size(eol - offset); - if (line_length < max_line_length) { - buf[line_length] = '\0'; + read(str, count, at); + auto eol = str.find_first_of("\n\r"sv); + if (eol != std::string::npos) { + auto next_line = str.find_first_not_of("\n\r"sv, eol); + str.resize(eol); + if (eol != std::string::npos) { + seek(last_offset + static_cast(next_line), SEEK_SET); + return eol; + } } - return {buf}; + // We did not necessarily find the end of the trailing newline sequence. + seek(last_offset, SEEK_SET); + findAndSkipNextEOL(); + return eol; +} + +std::string +InputSource::readLine(size_t max_line_length) +{ + return read_line(max_line_length); +} + +inline std::string +InputSource::read_line(size_t count, qpdf_offset_t at) +{ + std::string result(count, '\0'); + read_line(result, count, at); + return result; } bool diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index d38f4d7..e56fc22 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -2761,12 +2762,12 @@ QPDF::pipeStreamData( bool attempted_finish = false; try { - file->seek(offset, SEEK_SET); - auto buf = std::make_unique(length); - if (auto read = file->read(buf.get(), length); read != length) { - throw damagedPDF(*file, "", offset + toO(read), "unexpected EOF reading stream data"); + auto buf = file->read(length, offset); + if (buf.size() != length) { + throw damagedPDF( + *file, "", offset + toO(buf.size()), "unexpected EOF reading stream data"); } - pipeline->write(buf.get(), length); + pipeline->write(buf.data(), length); attempted_finish = true; pipeline->finish(); return true; diff --git a/libqpdf/QPDFTokenizer.cc b/libqpdf/QPDFTokenizer.cc index bf7fcf6..4275862 100644 --- a/libqpdf/QPDFTokenizer.cc +++ b/libqpdf/QPDFTokenizer.cc @@ -3,6 +3,7 @@ // DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of // including it in case it may accidentally be used. +#include #include #include #include diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index ac9abfa..e4ea689 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include using namespace qpdf; +using namespace std::literals; template static void @@ -96,68 +98,52 @@ QPDF::isLinearized() // The PDF spec says the linearization dictionary must be completely contained within the first // 1024 bytes of the file. Add a byte for a null terminator. - static int const tbuf_size = 1025; - - auto b = std::make_unique(tbuf_size); - char* buf = b.get(); - m->file->seek(0, SEEK_SET); - memset(buf, '\0', tbuf_size); - m->file->read(buf, tbuf_size - 1); - - int lindict_obj = -1; - char* p = buf; - while (lindict_obj == -1) { + auto buffer = m->file->read(1024, 0); + size_t pos = 0; + while (true) { // Find a digit or end of buffer - while (((p - buf) < tbuf_size) && (!util::is_digit(*p))) { - ++p; - } - if (p - buf == tbuf_size) { - break; + pos = buffer.find_first_of("0123456789"sv, pos); + if (pos == std::string::npos) { + return false; } // Seek to the digit. Then skip over digits for a potential // next iteration. - m->file->seek(p - buf, SEEK_SET); - while (((p - buf) < tbuf_size) && util::is_digit(*p)) { - ++p; + m->file->seek(toO(pos), SEEK_SET); + + auto t1 = readToken(*m->file, 20); + if (!(t1.isInteger() && readToken(*m->file, 6).isInteger() && + readToken(*m->file, 4).isWord("obj"))) { + pos = buffer.find_first_not_of("0123456789"sv, pos); + if (pos == std::string::npos) { + return false; + } + continue; } - QPDFTokenizer::Token t1 = readToken(*m->file); - if (t1.isInteger() && readToken(*m->file).isInteger() && - readToken(*m->file).isWord("obj") && - readToken(*m->file).getType() == QPDFTokenizer::tt_dict_open) { - lindict_obj = toI(QUtil::string_to_ll(t1.getValue().c_str())); + auto candidate = getObject(toI(QUtil::string_to_ll(t1.getValue().data())), 0); + if (!candidate.isDictionary()) { + return false; } - } - - if (lindict_obj <= 0) { - return false; - } - auto candidate = getObjectByID(lindict_obj, 0); - if (!candidate.isDictionary()) { - return false; - } - - QPDFObjectHandle linkey = candidate.getKey("/Linearized"); - if (!(linkey.isNumber() && (toI(floor(linkey.getNumericValue())) == 1))) { - return false; - } + auto linkey = candidate.getKey("/Linearized"); + if (!(linkey.isNumber() && toI(floor(linkey.getNumericValue())) == 1)) { + return false; + } - QPDFObjectHandle L = candidate.getKey("/L"); - if (L.isInteger()) { + auto L = candidate.getKey("/L"); + if (!L.isInteger()) { + return false; + } qpdf_offset_t Li = L.getIntValue(); m->file->seek(0, SEEK_END); if (Li != m->file->tell()) { QTC::TC("qpdf", "QPDF /L mismatch"); return false; - } else { - m->linp.file_size = Li; } + m->linp.file_size = Li; + m->lindict = candidate; + return true; } - - m->lindict = candidate; - - return true; } void @@ -548,7 +534,7 @@ QPDF::maxEnd(ObjUser const& ou) } qpdf_offset_t end = 0; for (auto const& og: m->obj_user_to_objects[ou]) { - if (m->obj_cache.count(og) == 0) { + if (!m->obj_cache.count(og)) { stopOnError("unknown object referenced in object user table"); } end = std::max(end, m->obj_cache[og].end_after_space); diff --git a/libqpdf/qpdf/InputSource_private.hh b/libqpdf/qpdf/InputSource_private.hh new file mode 100644 index 0000000..6194615 --- /dev/null +++ b/libqpdf/qpdf/InputSource_private.hh @@ -0,0 +1,78 @@ +#ifndef QPDF_INPUTSOURCE_PRIVATE_HH +#define QPDF_INPUTSOURCE_PRIVATE_HH + +#include + +inline size_t +InputSource::read(std::string& str, size_t count, qpdf_offset_t at) +{ + if (at >= 0) { + seek(at, SEEK_SET); + } + str.resize(count); + str.resize(read(str.data(), count)); + return str.size(); +} + +inline std::string +InputSource::read(size_t count, qpdf_offset_t at) +{ + std::string result(count, '\0'); + (void)read(result, count, at); + return result; +} + +inline void +InputSource::loadBuffer() +{ + buf_idx = 0; + buf_len = qpdf_offset_t(read(buffer, buf_size)); + // NB read sets last_offset + buf_start = last_offset; +} + +inline qpdf_offset_t +InputSource::fastTell() +{ + if (buf_len == 0) { + loadBuffer(); + } else { + auto curr = tell(); + if (curr < buf_start || curr >= (buf_start + buf_len)) { + loadBuffer(); + } else { + last_offset = curr; + buf_idx = curr - buf_start; + } + } + return last_offset; +} + +inline bool +InputSource::fastRead(char& ch) +{ + // Before calling fastRead, fastTell must be called to prepare the buffer. Once reading is + // complete, fastUnread must be called to set the correct file position. + if (buf_idx < buf_len) { + ch = buffer[buf_idx]; + ++(buf_idx); + ++(last_offset); + return true; + + } else if (buf_len == 0) { + return false; + } else { + seek(buf_start + buf_len, SEEK_SET); + fastTell(); + return fastRead(ch); + } +} + +inline void +InputSource::fastUnread(bool back) +{ + last_offset -= back ? 1 : 0; + seek(last_offset, SEEK_SET); +} + +#endif // QPDF_INPUTSOURCE_PRIVATE_HH diff --git a/manual/qpdf.1 b/manual/qpdf.1 index 38e30a8..03f4e10 100644 --- a/manual/qpdf.1 +++ b/manual/qpdf.1 @@ -3,7 +3,7 @@ .\" Edits will be automatically overwritten if the build is .\" run in maintainer mode. .\" -.TH QPDF "1" "" "qpdf version 12.0.0" "User Commands" +.TH QPDF "1" "" "qpdf version 12.0.1" "User Commands" .SH NAME qpdf \- PDF transformation software .SH SYNOPSIS diff --git a/manual/release-notes.rst b/manual/release-notes.rst index 763b1a0..7eae202 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -13,12 +13,20 @@ more detail. .. x.y.z: not yet released +12.0.1: not yet released + - Bug fixes + + - In ``QPDF::isLinearized`` return false if the first object in the file is + not a linearization parameter dictionary or its ``/L`` entry is not an + integer object. Previously the method returned false if the first + dictionary object was not a linearization parameter dictionary. + .. _r12-0-0: .. cSpell:ignore substract 12.0.0: March 9, 2025 - - API: breaking changes + - API breaking changes - The header file ``qpdf/QPDFObject.hh`` now generates an error if included. This is to prevent code that includes it from