Coverage Report

Created: 2025-10-10 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/Pl_Base64.cc
Line
Count
Source
1
#include <qpdf/Pl_Base64.hh>
2
3
#include <qpdf/QIntC.hh>
4
#include <qpdf/Util.hh>
5
6
#include <cstring>
7
#include <stdexcept>
8
9
using namespace qpdf;
10
11
static char
12
to_c(unsigned int ch)
13
0
{
14
0
    return static_cast<char>(ch);
15
0
}
16
17
static unsigned char
18
to_uc(int ch)
19
0
{
20
0
    return static_cast<unsigned char>(ch);
21
0
}
22
23
static int
24
to_i(int i)
25
0
{
26
0
    return static_cast<int>(i);
27
0
}
28
29
Pl_Base64::Pl_Base64(char const* identifier, Pipeline* next, action_e action) :
30
0
    Pipeline(identifier, next),
31
0
    action(action)
32
0
{
33
0
}
34
35
void
36
Pl_Base64::write(unsigned char const* data, size_t len)
37
0
{
38
0
    in_buffer.append(reinterpret_cast<const char*>(data), len);
39
0
}
40
41
std::string
42
Pl_Base64::decode(std::string_view data)
43
0
{
44
0
    Pl_Base64 p("base64-decode", nullptr, a_decode);
45
0
    p.decode_internal(data);
46
0
    return std::move(p.out_buffer);
47
0
}
48
49
std::string
50
Pl_Base64::encode(std::string_view data)
51
0
{
52
0
    Pl_Base64 p("base64-encode", nullptr, a_encode);
53
0
    p.encode_internal(data);
54
0
    return std::move(p.out_buffer);
55
0
}
56
57
void
58
Pl_Base64::decode_internal(std::string_view data)
59
0
{
60
0
    auto len = data.size();
61
0
    auto res = (len / 4u + 1u) * 3u;
62
0
    out_buffer.reserve(res);
63
0
    unsigned char const* p = reinterpret_cast<const unsigned char*>(data.data());
64
0
    while (len > 0) {
65
0
        if (!util::is_space(to_c(*p))) {
66
0
            buf[pos++] = *p;
67
0
            if (pos == 4) {
68
0
                flush_decode();
69
0
            }
70
0
        }
71
0
        ++p;
72
0
        --len;
73
0
    }
74
0
    if (pos > 0) {
75
0
        for (size_t i = pos; i < 4; ++i) {
76
0
            buf[i] = '=';
77
0
        }
78
0
        flush_decode();
79
0
    }
80
0
    qpdf_assert_debug(out_buffer.size() <= res);
81
0
}
82
83
void
84
Pl_Base64::encode_internal(std::string_view data)
85
0
{
86
0
    auto len = data.size();
87
0
    static const size_t max_len = (std::string().max_size() / 4u - 1u) * 3u;
88
    // Change to constexpr once AppImage is build with GCC >= 12
89
0
    if (len > max_len) {
90
0
        throw std::length_error(getIdentifier() + ": base64 decode: data exceeds maximum length");
91
0
    }
92
93
0
    auto res = (len / 3u + 1u) * 4u;
94
0
    out_buffer.reserve(res);
95
0
    unsigned char const* p = reinterpret_cast<const unsigned char*>(data.data());
96
0
    while (len > 0) {
97
0
        buf[pos++] = *p;
98
0
        if (pos == 3) {
99
0
            flush_encode();
100
0
        }
101
0
        ++p;
102
0
        --len;
103
0
    }
104
0
    if (pos > 0) {
105
0
        flush_encode();
106
0
    }
107
0
    qpdf_assert_debug(out_buffer.size() <= res);
108
0
}
109
110
void
111
Pl_Base64::flush_decode()
112
0
{
113
0
    if (end_of_data) {
114
0
        throw std::runtime_error(getIdentifier() + ": base64 decode: data follows pad characters");
115
0
    }
116
0
    size_t pad = 0;
117
0
    int shift = 18;
118
0
    int outval = 0;
119
0
    for (size_t i = 0; i < 4; ++i) {
120
0
        int v = 0;
121
0
        char ch = to_c(buf[i]);
122
0
        if (ch >= 'A' && ch <= 'Z') {
123
0
            v = ch - 'A';
124
0
        } else if (ch >= 'a' && ch <= 'z') {
125
0
            v = ch - 'a' + 26;
126
0
        } else if (ch >= '0' && ch <= '9') {
127
0
            v = ch - '0' + 52;
128
0
        } else if (ch == '+' || ch == '-') {
129
0
            v = 62;
130
0
        } else if (ch == '/' || ch == '_') {
131
0
            v = 63;
132
0
        } else if (ch == '=' && (i == 3 || (i == 2 && buf[3] == '='))) {
133
0
            ++pad;
134
0
            end_of_data = true;
135
0
            v = 0;
136
0
        } else {
137
0
            throw std::runtime_error(getIdentifier() + ": base64 decode: invalid input");
138
0
        }
139
0
        outval |= v << shift;
140
0
        shift -= 6;
141
0
    }
142
0
    unsigned char out[3] = {
143
0
        to_uc(outval >> 16),
144
0
        to_uc(0xff & (outval >> 8)),
145
0
        to_uc(0xff & outval),
146
0
    };
147
148
0
    out_buffer.append(reinterpret_cast<const char*>(out), 3u - pad);
149
0
    reset();
150
0
}
151
152
void
153
Pl_Base64::flush_encode()
154
0
{
155
0
    int outval = ((buf[0] << 16) | (buf[1] << 8) | buf[2]);
156
0
    unsigned char out[4] = {
157
0
        to_uc(outval >> 18),
158
0
        to_uc(0x3f & (outval >> 12)),
159
0
        to_uc(0x3f & (outval >> 6)),
160
0
        to_uc(0x3f & outval),
161
0
    };
162
0
    for (size_t i = 0; i < 4; ++i) {
163
0
        int ch = to_i(out[i]);
164
0
        if (ch < 26) {
165
0
            ch += 'A';
166
0
        } else if (ch < 52) {
167
0
            ch -= 26;
168
0
            ch += 'a';
169
0
        } else if (ch < 62) {
170
0
            ch -= 52;
171
0
            ch += '0';
172
0
        } else if (ch == 62) {
173
0
            ch = '+';
174
0
        } else if (ch == 63) {
175
0
            ch = '/';
176
0
        }
177
0
        out[i] = to_uc(ch);
178
0
    }
179
0
    for (size_t i = 0; i < 3 - pos; ++i) {
180
0
        out[3 - i] = '=';
181
0
    }
182
0
    out_buffer.append(reinterpret_cast<const char*>(out), 4);
183
0
    reset();
184
0
}
185
186
void
187
Pl_Base64::finish()
188
0
{
189
0
    if (action == a_decode) {
190
0
        decode_internal(in_buffer);
191
192
0
    } else {
193
0
        encode_internal(in_buffer);
194
0
    }
195
0
    if (next()) {
196
0
        in_buffer.clear();
197
0
        in_buffer.shrink_to_fit();
198
0
        next()->write(reinterpret_cast<unsigned char const*>(out_buffer.data()), out_buffer.size());
199
0
        out_buffer.clear();
200
0
        out_buffer.shrink_to_fit();
201
0
        next()->finish();
202
0
    }
203
0
}
204
205
void
206
Pl_Base64::reset()
207
0
{
208
0
    pos = 0;
209
0
    memset(buf, 0, 4);
210
0
}