Coverage Report

Created: 2025-08-29 06:57

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