Coverage Report

Created: 2025-10-12 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/Pl_Flate.cc
Line
Count
Source
1
#include <qpdf/Pl_Flate.hh>
2
3
#include <climits>
4
#include <cstring>
5
#include <zlib.h>
6
7
#include <qpdf/QIntC.hh>
8
#include <qpdf/QUtil.hh>
9
#include <qpdf/qpdf-config.h>
10
11
#ifdef ZOPFLI
12
# include <zopfli.h>
13
#endif
14
15
namespace
16
{
17
    unsigned long long memory_limit_{0};
18
} // namespace
19
20
int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION;
21
22
Pl_Flate::Members::Members(size_t out_bufsize, action_e action) :
23
90.4k
    out_bufsize(out_bufsize),
24
90.4k
    action(action),
25
90.4k
    initialized(false),
26
90.4k
    zdata(nullptr)
27
90.4k
{
28
90.4k
    this->outbuf = QUtil::make_shared_array<unsigned char>(out_bufsize);
29
    // Indirect through zdata to reach the z_stream so we don't have to include zlib.h in
30
    // Pl_Flate.hh.  This means people using shared library versions of qpdf don't have to have zlib
31
    // development files available, which particularly helps in a Windows environment.
32
90.4k
    zdata = new z_stream;
33
34
90.4k
    if (out_bufsize > UINT_MAX) {
35
0
        throw std::runtime_error(
36
0
            "Pl_Flate: zlib doesn't support buffer sizes larger than unsigned int");
37
0
    }
38
39
90.4k
    z_stream& zstream = *(static_cast<z_stream*>(this->zdata));
40
90.4k
    zstream.zalloc = nullptr;
41
90.4k
    zstream.zfree = nullptr;
42
90.4k
    zstream.opaque = nullptr;
43
90.4k
    zstream.next_in = nullptr;
44
90.4k
    zstream.avail_in = 0;
45
90.4k
    zstream.next_out = this->outbuf.get();
46
90.4k
    zstream.avail_out = QIntC::to_uint(out_bufsize);
47
48
90.4k
    if (action == a_deflate && Pl_Flate::zopfli_enabled()) {
49
0
        zopfli_buf = std::make_unique<std::string>();
50
0
    }
51
90.4k
}
52
53
Pl_Flate::Members::~Members()
54
90.4k
{
55
90.4k
    if (initialized) {
56
2.55k
        z_stream& zstream = *(static_cast<z_stream*>(zdata));
57
2.55k
        if (action == a_deflate) {
58
834
            deflateEnd(&zstream);
59
1.72k
        } else {
60
1.72k
            inflateEnd(&zstream);
61
1.72k
        }
62
2.55k
    }
63
64
90.4k
    delete static_cast<z_stream*>(this->zdata);
65
90.4k
    zdata = nullptr;
66
90.4k
}
67
68
Pl_Flate::Pl_Flate(
69
    char const* identifier, Pipeline* next, action_e action, unsigned int out_bufsize_int) :
70
90.4k
    Pipeline(identifier, next),
71
90.4k
    m(std::make_unique<Members>(QIntC::to_size(out_bufsize_int), action))
72
90.4k
{
73
90.4k
    if (!next) {
74
0
        throw std::logic_error("Attempt to create Pl_Flate with nullptr as next");
75
0
    }
76
90.4k
}
77
78
// Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
79
90.4k
Pl_Flate::~Pl_Flate() = default;
80
81
unsigned long long
82
Pl_Flate::memory_limit()
83
2.90k
{
84
2.90k
    return memory_limit_;
85
2.90k
}
86
87
void
88
Pl_Flate::memory_limit(unsigned long long limit)
89
24.4k
{
90
24.4k
    memory_limit_ = limit;
91
24.4k
}
92
93
void
94
Pl_Flate::setWarnCallback(std::function<void(char const*, int)> callback)
95
15.6k
{
96
15.6k
    m->callback = callback;
97
15.6k
}
98
99
void
100
Pl_Flate::warn(char const* msg, int code)
101
2.99k
{
102
2.99k
    if (m->callback) {
103
2.99k
        m->callback(msg, code);
104
2.99k
    }
105
2.99k
}
106
107
void
108
Pl_Flate::write(unsigned char const* data, size_t len)
109
33.5M
{
110
33.5M
    if (!m->outbuf) {
111
0
        throw std::logic_error(
112
0
            this->identifier + ": Pl_Flate: write() called after finish() called");
113
0
    }
114
33.5M
    if (m->zopfli_buf) {
115
0
        m->zopfli_buf->append(reinterpret_cast<char const*>(data), len);
116
0
        return;
117
0
    }
118
119
    // Write in chunks in case len is too big to fit in an int. Assume int is at least 32 bits.
120
33.5M
    static size_t const max_bytes = 1 << 30;
121
33.5M
    size_t bytes_left = len;
122
33.5M
    unsigned char const* buf = data;
123
67.1M
    while (bytes_left > 0) {
124
33.5M
        size_t bytes = (bytes_left >= max_bytes ? max_bytes : bytes_left);
125
33.5M
        handleData(buf, bytes, (m->action == a_inflate ? Z_SYNC_FLUSH : Z_NO_FLUSH));
126
33.5M
        bytes_left -= bytes;
127
33.5M
        buf += bytes;
128
33.5M
    }
129
33.5M
}
130
131
void
132
Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
133
33.6M
{
134
33.6M
    if (len > UINT_MAX) {
135
0
        throw std::runtime_error("Pl_Flate: zlib doesn't support data blocks larger than int");
136
0
    }
137
33.6M
    z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
138
    // zlib is known not to modify the data pointed to by next_in but doesn't declare the field
139
    // value const unless compiled to do so.
140
33.6M
    zstream.next_in = const_cast<unsigned char*>(data);
141
33.6M
    zstream.avail_in = QIntC::to_uint(len);
142
143
33.6M
    if (!m->initialized) {
144
84.4k
        int err = Z_OK;
145
146
        // deflateInit and inflateInit are macros that use old-style casts.
147
84.4k
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
148
84.4k
# pragma GCC diagnostic push
149
84.4k
# pragma GCC diagnostic ignored "-Wold-style-cast"
150
84.4k
#endif
151
84.4k
        if (m->action == a_deflate) {
152
71.3k
            err = deflateInit(&zstream, compression_level);
153
71.3k
        } else {
154
13.1k
            err = inflateInit(&zstream);
155
13.1k
        }
156
84.4k
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
157
84.4k
# pragma GCC diagnostic pop
158
84.4k
#endif
159
160
84.4k
        checkError("Init", err);
161
84.4k
        m->initialized = true;
162
84.4k
    }
163
164
33.6M
    int err = Z_OK;
165
166
33.6M
    bool done = false;
167
67.3M
    while (!done) {
168
33.6M
        if (m->action == a_deflate) {
169
33.4M
            err = deflate(&zstream, flush);
170
33.4M
        } else {
171
228k
            err = inflate(&zstream, flush);
172
228k
        }
173
33.6M
        if ((m->action == a_inflate) && (err != Z_OK) && zstream.msg &&
174
7.04k
            (strcmp(zstream.msg, "incorrect data check") == 0)) {
175
            // Other PDF readers ignore this specific error. Combining this with Z_SYNC_FLUSH
176
            // enables qpdf to handle some broken zlib streams without losing data.
177
4.05k
            err = Z_STREAM_END;
178
4.05k
        }
179
33.6M
        switch (err) {
180
2.99k
        case Z_BUF_ERROR:
181
            // Probably shouldn't be able to happen, but possible as a boundary condition: if the
182
            // last call to inflate exactly filled the output buffer, it's possible that the next
183
            // call to inflate could have nothing to do. There are PDF files in the wild that have
184
            // this error (including at least one in qpdf's test suite). In some cases, we want to
185
            // know about this, because it indicates incorrect compression, so call a callback if
186
            // provided.
187
2.99k
            warn("input stream is complete but output may still be valid", err);
188
2.99k
            done = true;
189
2.99k
            break;
190
191
88.2k
        case Z_STREAM_END:
192
88.2k
            done = true;
193
            // fall through
194
195
33.6M
        case Z_OK:
196
33.6M
            {
197
33.6M
                if ((zstream.avail_in == 0) && (zstream.avail_out > 0)) {
198
                    // There is nothing left to read, and there was sufficient buffer space to write
199
                    // everything we needed, so we're done for now.
200
33.6M
                    done = true;
201
33.6M
                }
202
33.6M
                uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out);
203
33.6M
                if (ready > 0) {
204
361k
                    if (memory_limit_ && m->action != a_deflate) {
205
197k
                        m->written += ready;
206
197k
                        if (m->written > memory_limit_) {
207
73
                            throw std::runtime_error("PL_Flate memory limit exceeded");
208
73
                        }
209
197k
                    }
210
361k
                    next()->write(m->outbuf.get(), ready);
211
361k
                    zstream.next_out = m->outbuf.get();
212
361k
                    zstream.avail_out = QIntC::to_uint(m->out_bufsize);
213
361k
                }
214
33.6M
            }
215
33.6M
            break;
216
217
33.6M
        default:
218
3.01k
            checkError("data", err);
219
3.01k
            break;
220
33.6M
        }
221
33.6M
    }
222
33.6M
}
223
224
void
225
Pl_Flate::finish()
226
86.4k
{
227
86.4k
    if (m->written > memory_limit_) {
228
72
        throw std::runtime_error("PL_Flate memory limit exceeded");
229
72
    }
230
86.3k
    try {
231
86.3k
        if (m->zopfli_buf) {
232
0
            finish_zopfli();
233
86.3k
        } else if (m->outbuf.get()) {
234
86.3k
            if (m->initialized) {
235
83.3k
                z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
236
83.3k
                unsigned char buf[1];
237
83.3k
                buf[0] = '\0';
238
83.3k
                handleData(buf, 0, Z_FINISH);
239
83.3k
                int err = Z_OK;
240
83.3k
                if (m->action == a_deflate) {
241
70.4k
                    err = deflateEnd(&zstream);
242
70.4k
                } else {
243
12.9k
                    err = inflateEnd(&zstream);
244
12.9k
                }
245
83.3k
                m->initialized = false;
246
83.3k
                checkError("End", err);
247
83.3k
            }
248
249
86.3k
            m->outbuf = nullptr;
250
86.3k
        }
251
86.3k
    } catch (std::exception& e) {
252
1.49k
        try {
253
1.49k
            next()->finish();
254
1.49k
        } catch (...) {
255
            // ignore secondary exception
256
3
        }
257
1.49k
        throw std::runtime_error(e.what());
258
1.49k
    }
259
84.8k
    next()->finish();
260
84.8k
}
261
262
void
263
Pl_Flate::setCompressionLevel(int level)
264
0
{
265
0
    compression_level = level;
266
0
}
267
268
void
269
Pl_Flate::checkError(char const* prefix, int error_code)
270
169k
{
271
169k
    z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
272
169k
    if (error_code != Z_OK) {
273
3.01k
        char const* action_str = (m->action == a_deflate ? "deflate" : "inflate");
274
3.01k
        std::string msg = identifier + ": " + action_str + ": " + prefix + ": ";
275
276
3.01k
        if (zstream.msg) {
277
2.98k
            msg += zstream.msg;
278
2.98k
        } else {
279
29
            switch (error_code) {
280
0
            case Z_ERRNO:
281
0
                msg += "zlib system error";
282
0
                break;
283
284
0
            case Z_STREAM_ERROR:
285
0
                msg += "zlib stream error";
286
0
                break;
287
288
0
            case Z_DATA_ERROR:
289
0
                msg += "zlib data error";
290
0
                break;
291
292
0
            case Z_MEM_ERROR:
293
0
                msg += "zlib memory error";
294
0
                break;
295
296
0
            case Z_BUF_ERROR:
297
0
                msg += "zlib buffer error";
298
0
                break;
299
300
0
            case Z_VERSION_ERROR:
301
0
                msg += "zlib version error";
302
0
                break;
303
304
29
            default:
305
29
                msg += std::string("zlib unknown error (") + std::to_string(error_code) + ")";
306
29
                break;
307
29
            }
308
29
        }
309
310
3.01k
        throw std::runtime_error(msg);
311
3.01k
    }
312
169k
}
313
314
void
315
Pl_Flate::finish_zopfli()
316
0
{
317
#ifdef ZOPFLI
318
    if (!m->zopfli_buf) {
319
        return;
320
    }
321
    auto buf = std::move(*m->zopfli_buf.release());
322
    ZopfliOptions z_opt;
323
    ZopfliInitOptions(&z_opt);
324
    unsigned char* out{nullptr};
325
    size_t out_size{0};
326
    ZopfliCompress(
327
        &z_opt,
328
        ZOPFLI_FORMAT_ZLIB,
329
        reinterpret_cast<unsigned char const*>(buf.c_str()),
330
        buf.size(),
331
        &out,
332
        &out_size);
333
    std::unique_ptr<unsigned char, decltype(&free)> p(out, &free);
334
    next()->write(out, out_size);
335
    // next()->finish is called by finish()
336
#endif
337
0
}
338
339
bool
340
Pl_Flate::zopfli_supported()
341
75.7k
{
342
#ifdef ZOPFLI
343
    return true;
344
#else
345
75.7k
    return false;
346
75.7k
#endif
347
75.7k
}
348
349
bool
350
Pl_Flate::zopfli_enabled()
351
75.7k
{
352
75.7k
    if (zopfli_supported()) {
353
0
        std::string value;
354
0
        static bool enabled = QUtil::get_env("QPDF_ZOPFLI", &value) && value != "disabled";
355
0
        return enabled;
356
75.7k
    } else {
357
75.7k
        return false;
358
75.7k
    }
359
75.7k
}
360
361
bool
362
Pl_Flate::zopfli_check_env(QPDFLogger* logger)
363
0
{
364
0
    if (Pl_Flate::zopfli_supported()) {
365
0
        return true;
366
0
    }
367
0
    std::string value;
368
0
    auto is_set = QUtil::get_env("QPDF_ZOPFLI", &value);
369
0
    if (!is_set || value == "disabled" || value == "silent") {
370
0
        return true;
371
0
    }
372
0
    if (!logger) {
373
0
        logger = QPDFLogger::defaultLogger().get();
374
0
    }
375
376
    // This behavior is known in QPDFJob (for the --zopfli argument), Pl_Flate.hh, README.md,
377
    // and the manual. Do a case-insensitive search for zopfli if changing the behavior.
378
0
    if (value == "force") {
379
0
        throw std::runtime_error("QPDF_ZOPFLI=force, and zopfli support is not enabled");
380
0
    }
381
0
    logger->warn("QPDF_ZOPFLI is set, but libqpdf was not built with zopfli support\n");
382
0
    logger->warn(
383
0
        "Set QPDF_ZOPFLI=silent to suppress this warning and use zopfli when available.\n");
384
0
    return false;
385
0
}