• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Thalhammer / jwt-cpp / 11404259838

18 Oct 2024 01:04PM UTC coverage: 94.974% (+0.02%) from 94.959%
11404259838

Pull #364

github

web-flow
Merge bed2b829d into 83703d718
Pull Request #364: Refactor base64 decoding to use a lookup table

9 of 9 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

1285 of 1353 relevant lines covered (94.97%)

255.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/include/jwt-cpp/base.h
1
#ifndef JWT_CPP_BASE_H
2
#define JWT_CPP_BASE_H
3

4
#include <algorithm>
5
#include <array>
6
#include <cstdint>
7
#include <stdexcept>
8
#include <string>
9
#include <vector>
10

11
#ifdef __has_cpp_attribute
12
#if __has_cpp_attribute(fallthrough)
13
#define JWT_FALLTHROUGH [[fallthrough]]
14
#endif
15
#endif
16

17
#ifndef JWT_FALLTHROUGH
18
#define JWT_FALLTHROUGH
19
#endif
20

21
namespace jwt {
22
        /**
23
         * \brief character maps when encoding and decoding
24
         */
25
        namespace alphabet {
26
                /**
27
                 * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
28
                 *
29
                 * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are
30
                 * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
31
                 */
32
                struct base64 {
33
                        static const std::array<char, 64>& data() {
7✔
34
                                static constexpr std::array<char, 64> data{
35
                                        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
36
                                         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
37
                                         'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
38
                                         'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}};
39
                                return data;
7✔
40
                        }
41
                        static const std::array<int8_t,256>& rdata() {
602✔
42
                                static constexpr std::array<int8_t, 256> rdata{
43
                                        {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
44
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
45
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
46
                                         52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
47
                                         -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
48
                                         15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
49
                                         -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
50
                                         41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
51
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
52
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
53
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
54
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
55
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
56
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
57
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
58
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,}};
59
                                return rdata;
602✔
60
                        }
61
                        static const std::string& fill() {
47✔
62
                                static const std::string fill{"="};
47✔
63
                                return fill;
47✔
64
                        }
65
                };
66
                /**
67
                 * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5)
68
                 *
69
                 * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL
70
                 * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states:
71
                 *
72
                 * > Base64 encoding using the URL- and filename-safe character set defined in
73
                 * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted
74
                 */
75
                struct base64url {
76
                        static const std::array<char, 64>& data() {
153✔
77
                                static constexpr std::array<char, 64> data{
78
                                        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
79
                                         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
80
                                         'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
81
                                         'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
82
                                return data;
153✔
83
                        }
84
                        static const std::array<int8_t,256>& rdata() {
903✔
85
                                static constexpr std::array<int8_t, 256> rdata{
86
                                        {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
87
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
88
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,
89
                                         52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
90
                                         -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
91
                                         15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,63,
92
                                         -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
93
                                         41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
94
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
95
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
96
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
97
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
98
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
99
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
100
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
101
                                         -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,}};
102
                                return rdata;
903✔
103
                        }
104
                        static const std::string& fill() {
946✔
105
                                static const std::string fill{"%3d"};
946✔
106
                                return fill;
946✔
107
                        }
108
                };
109
                namespace helper {
110
                        /**
111
                         * \brief A General purpose base64url alphabet respecting the
112
                         * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1)
113
                         *
114
                         * This is useful in situations outside of JWT encoding/decoding and is provided as a helper
115
                         */
116
                        struct base64url_percent_encoding {
117
                                static const std::array<char, 64>& data() {
118
                                        static constexpr std::array<char, 64> data{
119
                                                {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
120
                                                 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
121
                                                 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
122
                                                 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
123
                                        return data;
124
                                }
125
                                static const std::array<int8_t,256>& rdata() {
7✔
126
                                        static constexpr std::array<int8_t, 256> rdata{
127
                                                {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
128
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
129
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,
130
                                                52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
131
                                                -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
132
                                                15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,63,
133
                                                -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
134
                                                41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
135
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
136
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
137
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
138
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
139
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
140
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
141
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
142
                                                -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,}};
143
                                        return rdata;
7✔
144
                                }
145
                                static const std::vector<std::string>& fill() {
7✔
146
                                        static const std::vector<std::string> fill{"%3D", "%3d"};
9✔
147
                                        return fill;
7✔
148
                                }
149
                        };
150
                } // namespace helper
151

152
                inline uint32_t index(const std::array<int8_t, 256>& rdata, char symbol) {
35,691✔
153
                        auto index = rdata[static_cast<unsigned char>(symbol)];
35,691✔
154
                        if (index <= -1) {throw std::runtime_error("Invalid input: not within alphabet");}
35,691✔
155
                        return static_cast<uint32_t>(index);
35,691✔
156
                }
157
        } // namespace alphabet
158

159
        /**
160
         * \brief A collection of fellable functions for working with base64 and base64url
161
         */
162
        namespace base {
163
                namespace details {
164
                        struct padding {
165
                                size_t count = 0;
166
                                size_t length = 0;
167

168
                                padding() = default;
377✔
169
                                padding(size_t count, size_t length) : count(count), length(length) {}
515✔
170

171
                                padding operator+(const padding& p) { return padding(count + p.count, length + p.length); }
250✔
172

173
                                friend bool operator==(const padding& lhs, const padding& rhs) {
21✔
174
                                        return lhs.count == rhs.count && lhs.length == rhs.length;
21✔
175
                                }
176
                        };
177

178
                        inline padding count_padding(const std::string& base, const std::vector<std::string>& fills) {
621✔
179
                                for (const auto& fill : fills) {
1,031✔
180
                                        if (base.size() < fill.size()) continue;
660✔
181
                                        // Does the end of the input exactly match the fill pattern?
182
                                        if (base.substr(base.size() - fill.size()) == fill) {
634✔
183
                                                return padding{1, fill.length()} +
250✔
184
                                                           count_padding(base.substr(0, base.size() - fill.size()), fills);
500✔
185
                                        }
186
                                }
187

188
                                return {};
371✔
189
                        }
190

191
                        inline std::string encode(const std::string& bin, const std::array<char, 64>& alphabet,
156✔
192
                                                                          const std::string& fill) {
193
                                size_t size = bin.size();
156✔
194
                                std::string res;
156✔
195

196
                                // clear incomplete bytes
197
                                size_t fast_size = size - size % 3;
156✔
198
                                for (size_t i = 0; i < fast_size;) {
1,771✔
199
                                        uint32_t octet_a = static_cast<unsigned char>(bin[i++]);
1,615✔
200
                                        uint32_t octet_b = static_cast<unsigned char>(bin[i++]);
1,615✔
201
                                        uint32_t octet_c = static_cast<unsigned char>(bin[i++]);
1,615✔
202

203
                                        uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
1,615✔
204

205
                                        res += alphabet[(triple >> 3 * 6) & 0x3F];
1,615✔
206
                                        res += alphabet[(triple >> 2 * 6) & 0x3F];
1,615✔
207
                                        res += alphabet[(triple >> 1 * 6) & 0x3F];
1,615✔
208
                                        res += alphabet[(triple >> 0 * 6) & 0x3F];
1,615✔
209
                                }
210

211
                                if (fast_size == size) return res;
156✔
212

213
                                size_t mod = size % 3;
78✔
214

215
                                uint32_t octet_a = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
78✔
216
                                uint32_t octet_b = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
78✔
217
                                uint32_t octet_c = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
78✔
218

219
                                uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
78✔
220

221
                                switch (mod) {
78✔
222
                                case 1:
24✔
223
                                        res += alphabet[(triple >> 3 * 6) & 0x3F];
24✔
224
                                        res += alphabet[(triple >> 2 * 6) & 0x3F];
24✔
225
                                        res += fill;
24✔
226
                                        res += fill;
24✔
227
                                        break;
24✔
228
                                case 2:
54✔
229
                                        res += alphabet[(triple >> 3 * 6) & 0x3F];
54✔
230
                                        res += alphabet[(triple >> 2 * 6) & 0x3F];
54✔
231
                                        res += alphabet[(triple >> 1 * 6) & 0x3F];
54✔
232
                                        res += fill;
54✔
233
                                        break;
54✔
UNCOV
234
                                default: break;
×
235
                                }
236

237
                                return res;
78✔
UNCOV
238
                        }
×
239

240
                        inline std::string decode(const std::string& base, const std::array<int8_t, 256>& rdata,
350✔
241
                                                                          const std::vector<std::string>& fill) {
242
                                const auto pad = count_padding(base, fill);
350✔
243
                                if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill");
350✔
244

245
                                const size_t size = base.size() - pad.length;
349✔
246
                                if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size");
349✔
247

248
                                size_t out_size = size / 4 * 3;
349✔
249
                                std::string res;
349✔
250
                                res.reserve(out_size);
349✔
251

252
                                auto get_sextet = [&](size_t offset) { return alphabet::index(rdata, base[offset]); };
35,557✔
253

254
                                size_t fast_size = size - size % 4;
349✔
255
                                for (size_t i = 0; i < fast_size;) {
9,145✔
256
                                        uint32_t sextet_a = get_sextet(i++);
8,796✔
257
                                        uint32_t sextet_b = get_sextet(i++);
8,796✔
258
                                        uint32_t sextet_c = get_sextet(i++);
8,796✔
259
                                        uint32_t sextet_d = get_sextet(i++);
8,796✔
260

261
                                        uint32_t triple =
8,796✔
262
                                                (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6);
8,796✔
263

264
                                        res += static_cast<char>((triple >> 2 * 8) & 0xFFU);
8,796✔
265
                                        res += static_cast<char>((triple >> 1 * 8) & 0xFFU);
8,796✔
266
                                        res += static_cast<char>((triple >> 0 * 8) & 0xFFU);
8,796✔
267
                                }
268

269
                                if (pad.count == 0) return res;
349✔
270

271
                                uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6);
146✔
272

273
                                switch (pad.count) {
146✔
274
                                case 1:
81✔
275
                                        triple |= (get_sextet(fast_size + 2) << 1 * 6);
81✔
276
                                        res += static_cast<char>((triple >> 2 * 8) & 0xFFU);
81✔
277
                                        res += static_cast<char>((triple >> 1 * 8) & 0xFFU);
81✔
278
                                        break;
81✔
279
                                case 2: res += static_cast<char>((triple >> 2 * 8) & 0xFFU); break;
65✔
UNCOV
280
                                default: break;
×
281
                                }
282

283
                                return res;
146✔
UNCOV
284
                        }
×
285

286
                        inline std::string decode(const std::string& base, const std::array<int8_t, 256>& rdata,
343✔
287
                                                                          const std::string& fill) {
288
                                return decode(base, rdata, std::vector<std::string>{fill});
1,030✔
289
                        }
290

291
                        inline std::string pad(const std::string& base, const std::string& fill) {
339✔
292
                                std::string padding;
339✔
293
                                switch (base.size() % 4) {
339✔
294
                                case 1: padding += fill; JWT_FALLTHROUGH;
1✔
295
                                case 2: padding += fill; JWT_FALLTHROUGH;
53✔
296
                                case 3: padding += fill; JWT_FALLTHROUGH;
126✔
297
                                default: break;
339✔
298
                                }
299

300
                                return base + padding;
678✔
301
                        }
339✔
302

303
                        inline std::string trim(const std::string& base, const std::string& fill) {
155✔
304
                                auto pos = base.find(fill);
155✔
305
                                return base.substr(0, pos);
155✔
306
                        }
307
                } // namespace details
308

309
                /**
310
                 * \brief Generic base64 encoding
311
                 * 
312
                 * A Generic base64 encode function that supports any "alphabet"
313
                 * such as jwt::alphabet::base64 
314
                 * 
315
                 * \code
316
                 * const auto b64 = jwt::base::encode<jwt::alphabet::base64>("example_data")
317
                 * \endcode
318
                 */
319
                template<typename T>
320
                std::string encode(const std::string& bin) {
156✔
321
                        return details::encode(bin, T::data(), T::fill());
156✔
322
                }
323
                /**
324
                 * \brief Generic base64 decoding
325
                 * 
326
                 * A Generic base64 decoding function that supports any "alphabet"
327
                 * such as jwt::alphabet::base64 
328
                 * 
329
                 * \code
330
                 * const auto b64 = jwt::base::decode<jwt::alphabet::base64>("ZXhhbXBsZV9kYXRh")
331
                 * \endcode
332
                 */
333
                template<typename T>
334
                std::string decode(const std::string& base) {
350✔
335
                        return details::decode(base, T::rdata(), T::fill());
350✔
336
                }
337
                /**
338
                 * \brief Generic base64 padding
339
                 * 
340
                 * A Generic base64 pad function that supports any "alphabet"
341
                 * such as jwt::alphabet::base64 
342
                 * 
343
                 * \code
344
                 * const auto b64 = jwt::base::pad<jwt::alphabet::base64>("ZXhhbXBsZV9kYQ")
345
                 * \endcode
346
                 */
347
                template<typename T>
348
                std::string pad(const std::string& base) {
339✔
349
                        return details::pad(base, T::fill());
339✔
350
                }
351
                /**
352
                 * \brief Generic base64 trimming
353
                 * 
354
                 * A Generic base64 trim function that supports any "alphabet"
355
                 * such as jwt::alphabet::base64 
356
                 * 
357
                 * \code
358
                 * const auto b64 = jwt::base::trim<jwt::alphabet::base64>("ZXhhbXBsZV9kYQ==")
359
                 * \endcode
360
                 */
361
                template<typename T>
362
                std::string trim(const std::string& base) {
155✔
363
                        return details::trim(base, T::fill());
155✔
364
                }
365
        } // namespace base
366
} // namespace jwt
367

368
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc