Coverage Report

Created: 2024-09-08 06:04

/src/qpdf/libqpdf/Pl_Flate.cc
Line
Count
Source (jump to first uncovered line)
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
10
namespace
11
{
12
    unsigned long long memory_limit{0};
13
} // namespace
14
15
int Pl_Flate::compression_level = Z_DEFAULT_COMPRESSION;
16
17
Pl_Flate::Members::Members(size_t out_bufsize, action_e action) :
18
    out_bufsize(out_bufsize),
19
    action(action),
20
    initialized(false),
21
    zdata(nullptr)
22
2.13k
{
23
2.13k
    this->outbuf = QUtil::make_shared_array<unsigned char>(out_bufsize);
24
    // Indirect through zdata to reach the z_stream so we don't have to include zlib.h in
25
    // Pl_Flate.hh.  This means people using shared library versions of qpdf don't have to have zlib
26
    // development files available, which particularly helps in a Windows environment.
27
2.13k
    this->zdata = new z_stream;
28
29
2.13k
    if (out_bufsize > UINT_MAX) {
30
0
        throw std::runtime_error(
31
0
            "Pl_Flate: zlib doesn't support buffer sizes larger than unsigned int");
32
0
    }
33
34
2.13k
    z_stream& zstream = *(static_cast<z_stream*>(this->zdata));
35
2.13k
    zstream.zalloc = nullptr;
36
2.13k
    zstream.zfree = nullptr;
37
2.13k
    zstream.opaque = nullptr;
38
2.13k
    zstream.next_in = nullptr;
39
2.13k
    zstream.avail_in = 0;
40
2.13k
    zstream.next_out = this->outbuf.get();
41
2.13k
    zstream.avail_out = QIntC::to_uint(out_bufsize);
42
2.13k
}
43
44
Pl_Flate::Members::~Members()
45
2.13k
{
46
2.13k
    if (this->initialized) {
47
0
        z_stream& zstream = *(static_cast<z_stream*>(this->zdata));
48
0
        if (action == a_deflate) {
49
0
            deflateEnd(&zstream);
50
0
        } else {
51
0
            inflateEnd(&zstream);
52
0
        }
53
0
    }
54
55
2.13k
    delete static_cast<z_stream*>(this->zdata);
56
2.13k
    this->zdata = nullptr;
57
2.13k
}
58
59
Pl_Flate::Pl_Flate(
60
    char const* identifier, Pipeline* next, action_e action, unsigned int out_bufsize_int) :
61
    Pipeline(identifier, next),
62
    m(new Members(QIntC::to_size(out_bufsize_int), action))
63
2.13k
{
64
2.13k
}
65
66
Pl_Flate::~Pl_Flate() // NOLINT (modernize-use-equals-default)
67
2.13k
{
68
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
69
2.13k
}
70
71
void
72
Pl_Flate::setMemoryLimit(unsigned long long limit)
73
0
{
74
0
    memory_limit = limit;
75
0
}
76
77
void
78
Pl_Flate::setWarnCallback(std::function<void(char const*, int)> callback)
79
0
{
80
0
    m->callback = callback;
81
0
}
82
83
void
84
Pl_Flate::warn(char const* msg, int code)
85
0
{
86
0
    if (m->callback != nullptr) {
87
0
        m->callback(msg, code);
88
0
    }
89
0
}
90
91
void
92
Pl_Flate::write(unsigned char const* data, size_t len)
93
2.13k
{
94
2.13k
    if (m->outbuf == nullptr) {
95
0
        throw std::logic_error(
96
0
            this->identifier + ": Pl_Flate: write() called after finish() called");
97
0
    }
98
99
    // Write in chunks in case len is too big to fit in an int. Assume int is at least 32 bits.
100
2.13k
    static size_t const max_bytes = 1 << 30;
101
2.13k
    size_t bytes_left = len;
102
2.13k
    unsigned char const* buf = data;
103
4.27k
    while (bytes_left > 0) {
104
2.13k
        size_t bytes = (bytes_left >= max_bytes ? max_bytes : bytes_left);
105
2.13k
        handleData(buf, bytes, (m->action == a_inflate ? Z_SYNC_FLUSH : Z_NO_FLUSH));
106
2.13k
        bytes_left -= bytes;
107
2.13k
        buf += bytes;
108
2.13k
    }
109
2.13k
}
110
111
void
112
Pl_Flate::handleData(unsigned char const* data, size_t len, int flush)
113
4.27k
{
114
4.27k
    if (len > UINT_MAX) {
115
0
        throw std::runtime_error("Pl_Flate: zlib doesn't support data blocks larger than int");
116
0
    }
117
4.27k
    z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
118
    // zlib is known not to modify the data pointed to by next_in but doesn't declare the field
119
    // value const unless compiled to do so.
120
4.27k
    zstream.next_in = const_cast<unsigned char*>(data);
121
4.27k
    zstream.avail_in = QIntC::to_uint(len);
122
123
4.27k
    if (!m->initialized) {
124
2.13k
        int err = Z_OK;
125
126
        // deflateInit and inflateInit are macros that use old-style casts.
127
2.13k
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
128
2.13k
# pragma GCC diagnostic push
129
2.13k
# pragma GCC diagnostic ignored "-Wold-style-cast"
130
2.13k
#endif
131
2.13k
        if (m->action == a_deflate) {
132
2.13k
            err = deflateInit(&zstream, compression_level);
133
2.13k
        } else {
134
0
            err = inflateInit(&zstream);
135
0
        }
136
2.13k
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
137
2.13k
# pragma GCC diagnostic pop
138
2.13k
#endif
139
140
2.13k
        checkError("Init", err);
141
2.13k
        m->initialized = true;
142
2.13k
    }
143
144
4.27k
    int err = Z_OK;
145
146
4.27k
    bool done = false;
147
9.52k
    while (!done) {
148
5.24k
        if (m->action == a_deflate) {
149
5.24k
            err = deflate(&zstream, flush);
150
5.24k
        } else {
151
0
            err = inflate(&zstream, flush);
152
0
        }
153
5.24k
        if ((m->action == a_inflate) && (err != Z_OK) && zstream.msg &&
154
5.24k
            (strcmp(zstream.msg, "incorrect data check") == 0)) {
155
            // Other PDF readers ignore this specific error. Combining this with Z_SYNC_FLUSH
156
            // enables qpdf to handle some broken zlib streams without losing data.
157
0
            err = Z_STREAM_END;
158
0
        }
159
5.24k
        switch (err) {
160
0
        case Z_BUF_ERROR:
161
            // Probably shouldn't be able to happen, but possible as a boundary condition: if the
162
            // last call to inflate exactly filled the output buffer, it's possible that the next
163
            // call to inflate could have nothing to do. There are PDF files in the wild that have
164
            // this error (including at least one in qpdf's test suite). In some cases, we want to
165
            // know about this, because it indicates incorrect compression, so call a callback if
166
            // provided.
167
0
            this->warn("input stream is complete but output may still be valid", err);
168
0
            done = true;
169
0
            break;
170
171
2.13k
        case Z_STREAM_END:
172
2.13k
            done = true;
173
            // fall through
174
175
5.24k
        case Z_OK:
176
5.24k
            {
177
5.24k
                if ((zstream.avail_in == 0) && (zstream.avail_out > 0)) {
178
                    // There is nothing left to read, and there was sufficient buffer space to write
179
                    // everything we needed, so we're done for now.
180
4.27k
                    done = true;
181
4.27k
                }
182
5.24k
                uLong ready = QIntC::to_ulong(m->out_bufsize - zstream.avail_out);
183
5.24k
                if (ready > 0) {
184
5.24k
                    if (memory_limit) {
185
0
                        m->written += ready;
186
0
                        if (m->written > memory_limit) {
187
0
                            throw std::runtime_error("PL_Flate memory limit exceeded");
188
0
                        }
189
0
                    }
190
5.24k
                    this->getNext()->write(m->outbuf.get(), ready);
191
5.24k
                    zstream.next_out = m->outbuf.get();
192
5.24k
                    zstream.avail_out = QIntC::to_uint(m->out_bufsize);
193
5.24k
                }
194
5.24k
            }
195
5.24k
            break;
196
197
5.24k
        default:
198
0
            this->checkError("data", err);
199
0
            break;
200
5.24k
        }
201
5.24k
    }
202
4.27k
}
203
204
void
205
Pl_Flate::finish()
206
2.13k
{
207
2.13k
    if (m->written > memory_limit) {
208
0
        return;
209
0
    }
210
2.13k
    try {
211
2.13k
        if (m->outbuf.get()) {
212
2.13k
            if (m->initialized) {
213
2.13k
                z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
214
2.13k
                unsigned char buf[1];
215
2.13k
                buf[0] = '\0';
216
2.13k
                handleData(buf, 0, Z_FINISH);
217
2.13k
                int err = Z_OK;
218
2.13k
                if (m->action == a_deflate) {
219
2.13k
                    err = deflateEnd(&zstream);
220
2.13k
                } else {
221
0
                    err = inflateEnd(&zstream);
222
0
                }
223
2.13k
                m->initialized = false;
224
2.13k
                checkError("End", err);
225
2.13k
            }
226
227
2.13k
            m->outbuf = nullptr;
228
2.13k
        }
229
2.13k
    } catch (std::exception& e) {
230
0
        try {
231
0
            this->getNext()->finish();
232
0
        } catch (...) {
233
            // ignore secondary exception
234
0
        }
235
0
        throw std::runtime_error(e.what());
236
0
    }
237
2.13k
    this->getNext()->finish();
238
2.13k
}
239
240
void
241
Pl_Flate::setCompressionLevel(int level)
242
0
{
243
0
    compression_level = level;
244
0
}
245
246
void
247
Pl_Flate::checkError(char const* prefix, int error_code)
248
4.27k
{
249
4.27k
    z_stream& zstream = *(static_cast<z_stream*>(m->zdata));
250
4.27k
    if (error_code != Z_OK) {
251
0
        char const* action_str = (m->action == a_deflate ? "deflate" : "inflate");
252
0
        std::string msg = this->identifier + ": " + action_str + ": " + prefix + ": ";
253
254
0
        if (zstream.msg) {
255
0
            msg += zstream.msg;
256
0
        } else {
257
0
            switch (error_code) {
258
0
            case Z_ERRNO:
259
0
                msg += "zlib system error";
260
0
                break;
261
262
0
            case Z_STREAM_ERROR:
263
0
                msg += "zlib stream error";
264
0
                break;
265
266
0
            case Z_DATA_ERROR:
267
0
                msg += "zlib data error";
268
0
                break;
269
270
0
            case Z_MEM_ERROR:
271
0
                msg += "zlib memory error";
272
0
                break;
273
274
0
            case Z_BUF_ERROR:
275
0
                msg += "zlib buffer error";
276
0
                break;
277
278
0
            case Z_VERSION_ERROR:
279
0
                msg += "zlib version error";
280
0
                break;
281
282
0
            default:
283
0
                msg += std::string("zlib unknown error (") + std::to_string(error_code) + ")";
284
0
                break;
285
0
            }
286
0
        }
287
288
0
        throw std::runtime_error(msg);
289
0
    }
290
4.27k
}