Coverage Report

Created: 2025-06-22 06:28

/src/qpdf/libqpdf/Pl_RunLength.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/Pl_RunLength.hh>
2
3
#include <qpdf/QTC.hh>
4
#include <qpdf/QUtil.hh>
5
6
namespace
7
{
8
    unsigned long long memory_limit{0};
9
} // namespace
10
11
class Pl_RunLength::Members
12
{
13
  public:
14
    Members(action_e action) :
15
7.10k
        action(action)
16
7.10k
    {
17
7.10k
    }
18
    Members(Members const&) = delete;
19
7.10k
    ~Members() = default;
20
21
    action_e action;
22
    state_e state{st_top};
23
    unsigned char buf[128];
24
    unsigned int length{0};
25
    std::string out;
26
};
27
28
Pl_RunLength::Pl_RunLength(char const* identifier, Pipeline* next, action_e action) :
29
7.10k
    Pipeline(identifier, next),
30
7.10k
    m(std::make_unique<Members>(action))
31
7.10k
{
32
7.10k
    if (!next) {
33
0
        throw std::logic_error("Attempt to create Pl_RunLength with nullptr as next");
34
0
    }
35
7.10k
}
36
37
void
38
Pl_RunLength::setMemoryLimit(unsigned long long limit)
39
22.4k
{
40
22.4k
    memory_limit = limit;
41
22.4k
}
42
43
Pl_RunLength::~Pl_RunLength() // NOLINT (modernize-use-equals-default)
44
7.10k
{
45
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
46
7.10k
}
47
48
void
49
Pl_RunLength::write(unsigned char const* data, size_t len)
50
63.1k
{
51
63.1k
    if (m->action == a_encode) {
52
0
        encode(data, len);
53
63.1k
    } else {
54
63.1k
        decode(data, len);
55
63.1k
    }
56
63.1k
}
57
58
void
59
Pl_RunLength::encode(unsigned char const* data, size_t len)
60
0
{
61
0
    for (size_t i = 0; i < len; ++i) {
62
0
        if ((m->state == st_top) != (m->length <= 1)) {
63
0
            throw std::logic_error("Pl_RunLength::encode: state/length inconsistency");
64
0
        }
65
0
        unsigned char ch = data[i];
66
0
        if ((m->length > 0) && ((m->state == st_copying) || (m->length < 128)) &&
67
0
            (ch == m->buf[m->length - 1])) {
68
0
            QTC::TC("libtests", "Pl_RunLength: switch to run", (m->length == 128) ? 0 : 1);
69
0
            if (m->state == st_copying) {
70
0
                --m->length;
71
0
                flush_encode();
72
0
                m->buf[0] = ch;
73
0
                m->length = 1;
74
0
            }
75
0
            m->state = st_run;
76
0
            m->buf[m->length] = ch;
77
0
            ++m->length;
78
0
        } else {
79
0
            if ((m->length == 128) || (m->state == st_run)) {
80
0
                flush_encode();
81
0
            } else if (m->length > 0) {
82
0
                m->state = st_copying;
83
0
            }
84
0
            m->buf[m->length] = ch;
85
0
            ++m->length;
86
0
        }
87
0
    }
88
0
}
89
90
void
91
Pl_RunLength::decode(unsigned char const* data, size_t len)
92
63.1k
{
93
63.1k
    if (memory_limit && (len + m->out.size()) > memory_limit) {
94
1
        throw std::runtime_error("Pl_RunLength memory limit exceeded");
95
1
    }
96
63.1k
    m->out.reserve(len);
97
34.2M
    for (size_t i = 0; i < len; ++i) {
98
34.1M
        unsigned char const& ch = data[i];
99
34.1M
        switch (m->state) {
100
16.3M
        case st_top:
101
16.3M
            if (ch < 128) {
102
                // length represents remaining number of bytes to copy
103
101k
                m->length = 1U + ch;
104
101k
                m->state = st_copying;
105
16.2M
            } else if (ch > 128) {
106
                // length represents number of copies of next byte
107
16.2M
                m->length = 257U - ch;
108
16.2M
                m->state = st_run;
109
16.2M
            } else // ch == 128
110
1.36k
            {
111
                // EOD; stay in this state
112
1.36k
            }
113
16.3M
            break;
114
115
1.47M
        case st_copying:
116
1.47M
            m->out.append(1, static_cast<char>(ch));
117
1.47M
            if (--m->length == 0) {
118
98.5k
                m->state = st_top;
119
98.5k
            }
120
1.47M
            break;
121
122
16.2M
        case st_run:
123
16.2M
            m->out.append(m->length, static_cast<char>(ch));
124
16.2M
            m->state = st_top;
125
16.2M
            break;
126
34.1M
        }
127
34.1M
    }
128
63.1k
}
129
130
void
131
Pl_RunLength::flush_encode()
132
0
{
133
0
    if (m->length == 128) {
134
0
        QTC::TC(
135
0
            "libtests",
136
0
            "Pl_RunLength flush full buffer",
137
0
            (m->state == st_copying   ? 0
138
0
                 : m->state == st_run ? 1
139
0
                                      : -1));
140
0
    }
141
0
    if (m->length == 0) {
142
0
        QTC::TC("libtests", "Pl_RunLength flush empty buffer");
143
0
    }
144
0
    if (m->state == st_run) {
145
0
        if ((m->length < 2) || (m->length > 128)) {
146
0
            throw std::logic_error("Pl_RunLength: invalid length in flush_encode for run");
147
0
        }
148
0
        auto ch = static_cast<unsigned char>(257 - m->length);
149
0
        next()->write(&ch, 1);
150
0
        next()->write(&m->buf[0], 1);
151
0
    } else if (m->length > 0) {
152
0
        auto ch = static_cast<unsigned char>(m->length - 1);
153
0
        next()->write(&ch, 1);
154
0
        next()->write(m->buf, m->length);
155
0
    }
156
0
    m->state = st_top;
157
0
    m->length = 0;
158
0
}
159
160
void
161
Pl_RunLength::finish()
162
4.71k
{
163
    // When decoding, we might have read a length byte not followed by data, which means the stream
164
    // was terminated early, but we will just ignore this case since this is the only sensible thing
165
    // to do.
166
4.71k
    if (m->action == a_encode) {
167
0
        flush_encode();
168
0
        unsigned char ch = 128;
169
0
        next()->write(&ch, 1);
170
4.71k
    } else {
171
4.71k
        if (memory_limit && (m->out.size()) > memory_limit) {
172
111
            throw std::runtime_error("Pl_RunLength memory limit exceeded");
173
111
        }
174
4.60k
        next()->writeString(m->out);
175
4.60k
    }
176
4.60k
    next()->finish();
177
4.60k
}