Skip to content

Commit 5b418af

Browse files
committed
Addition of string_view support
1 parent f3f091b commit 5b418af

File tree

11 files changed

+691
-387
lines changed

11 files changed

+691
-387
lines changed

docs/traits.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ struct my_favorite_json_library_traits {
5454
static boolean_type as_boolean(const value_type &val);
5555

5656
// serialization and parsing
57-
static bool parse(value_type &val, string_type str);
57+
template <class string_t> // could be the json string_type, or std::string_view for instance
58+
static bool parse(value_type &val, const string_t& str);
5859
static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation
5960
};
6061
```

include/jwt-cpp/base.h

+137-81
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
#define JWT_CPP_BASE_H
33

44
#include <algorithm>
5-
#include <array>
65
#include <cstdint>
76
#include <stdexcept>
87
#include <string>
9-
#include <vector>
8+
9+
#include "string_types.h"
1010

1111
#ifdef __has_cpp_attribute
1212
#if __has_cpp_attribute(fallthrough)
@@ -18,6 +18,11 @@
1818
#define JWT_FALLTHROUGH
1919
#endif
2020

21+
#ifndef JWT_HAS_STRING_VIEW
22+
#include <array>
23+
#include <cstring>
24+
#endif
25+
2126
namespace jwt {
2227
/**
2328
* \brief character maps when encoding and decoding
@@ -30,19 +35,31 @@ namespace jwt {
3035
* base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
3136
*/
3237
struct base64 {
38+
39+
#define JWT_BASE_ALPHABET \
40+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', \
41+
'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', \
42+
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
43+
44+
#ifdef JWT_HAS_STRING_VIEW
45+
// From C++17 it's perfectly fine to have inline static variables. No ODR violation in this case.
46+
static constexpr char kData[]{JWT_BASE_ALPHABET, '+', '/'};
47+
48+
static constexpr std::string_view kFill[]{"="};
49+
#else
50+
// For pre C++17 standards, we need to use a method
3351
static const std::array<char, 64>& data() {
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;
52+
static constexpr std::array<char, 64> kData{{JWT_BASE_ALPHABET, '+', '/'}};
53+
return kData;
4054
}
41-
static const std::string& fill() {
42-
static std::string fill{"="};
43-
return fill;
55+
56+
static const std::array<const char*, 1>& fill() {
57+
static constexpr std::array<const char*, 1> kFill{"="};
58+
return kFill;
4459
}
60+
#endif
4561
};
62+
4663
/**
4764
* \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5)
4865
*
@@ -53,18 +70,24 @@ namespace jwt {
5370
* > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted
5471
*/
5572
struct base64url {
73+
74+
#ifdef JWT_HAS_STRING_VIEW
75+
static constexpr char kData[]{JWT_BASE_ALPHABET, '-', '_'};
76+
77+
static constexpr std::string_view kFill[]{"%3d"};
78+
#else
79+
// For pre C++17 standards, we need to use a method
5680
static const std::array<char, 64>& data() {
57-
static constexpr std::array<char, 64> data{
58-
{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
59-
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
60-
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
61-
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
62-
return data;
81+
static constexpr std::array<char, 64> kData{{JWT_BASE_ALPHABET, '-', '_'}};
82+
return kData;
6383
}
64-
static const std::string& fill() {
65-
static std::string fill{"%3d"};
66-
return fill;
84+
85+
static const std::array<const char*, 1>& fill() {
86+
static constexpr std::array<const char*, 1> kFill{"%3d"};
87+
return kFill;
6788
}
89+
90+
#endif
6891
};
6992
namespace helper {
7093
/**
@@ -74,26 +97,35 @@ namespace jwt {
7497
* This is useful in situations outside of JWT encoding/decoding and is provided as a helper
7598
*/
7699
struct base64url_percent_encoding {
100+
101+
#ifdef JWT_HAS_STRING_VIEW
102+
static constexpr char kData[]{JWT_BASE_ALPHABET, '-', '_'};
103+
104+
static constexpr std::string_view kFill[]{"%3D", "%3d"};
105+
#else
106+
// For pre C++17 standards, we need to use a method
77107
static const std::array<char, 64>& data() {
78-
static constexpr std::array<char, 64> data{
79-
{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
80-
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
81-
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
82-
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}};
83-
return data;
108+
static constexpr std::array<char, 64> kData{{JWT_BASE_ALPHABET, '-', '_'}};
109+
return kData;
84110
}
85-
static const std::initializer_list<std::string>& fill() {
86-
static std::initializer_list<std::string> fill{"%3D", "%3d"};
87-
return fill;
111+
112+
static const std::array<const char*, 2>& fill() {
113+
static constexpr std::array<const char*, 2> kFill{"%3D", "%3d"};
114+
return kFill;
88115
}
116+
#endif
89117
};
90118
} // namespace helper
91119

92-
inline uint32_t index(const std::array<char, 64>& alphabet, char symbol) {
93-
auto itr = std::find_if(alphabet.cbegin(), alphabet.cend(), [symbol](char c) { return c == symbol; });
94-
if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); }
120+
template<class char_it>
121+
inline uint32_t index(char_it alphabetBeg, char_it alphabetEnd, char symbol) {
122+
if (symbol >= 'A' && symbol <= 'Z') { return static_cast<uint32_t>(symbol - 'A'); }
123+
if (symbol >= 'a' && symbol <= 'z') { return static_cast<uint32_t>(26 + symbol - 'a'); }
124+
if (symbol >= '0' && symbol <= '9') { return static_cast<uint32_t>(52 + symbol - '0'); }
125+
auto itr = std::find(std::next(alphabetBeg, 62U), alphabetEnd, symbol);
126+
if (itr == alphabetEnd) { throw std::runtime_error("Invalid input: not within alphabet"); }
95127

96-
return std::distance(alphabet.cbegin(), itr);
128+
return static_cast<uint32_t>(std::distance(alphabetBeg, itr));
97129
}
98130
} // namespace alphabet
99131

@@ -108,39 +140,44 @@ namespace jwt {
108140
size_t length = 0;
109141

110142
padding() = default;
111-
padding(size_t count, size_t length) : count(count), length(length) {}
112143

113-
padding operator+(const padding& p) { return padding(count + p.count, length + p.length); }
144+
padding(size_t c, size_t l) : count(c), length(l) {}
114145

115-
friend bool operator==(const padding& lhs, const padding& rhs) {
116-
return lhs.count == rhs.count && lhs.length == rhs.length;
117-
}
146+
padding operator+(const padding& p) const { return padding{count + p.count, length + p.length}; }
118147
};
119148

120-
inline padding count_padding(const std::string& base, const std::vector<std::string>& fills) {
121-
for (const auto& fill : fills) {
122-
if (base.size() < fill.size()) continue;
123-
// Does the end of the input exactly match the fill pattern?
124-
if (base.substr(base.size() - fill.size()) == fill) {
125-
return padding{1, fill.length()} +
126-
count_padding(base.substr(0, base.size() - fill.size()), fills);
149+
inline std::size_t string_len(string_view str) { return str.size(); }
150+
151+
template<class str_input_it>
152+
padding count_padding(string_view base, str_input_it fillStart, str_input_it fillEnd) {
153+
for (str_input_it fillIt = fillStart; fillIt != fillEnd; ++fillIt) {
154+
std::size_t fillLen = string_len(*fillIt);
155+
if (base.size() >= fillLen) {
156+
std::size_t deltaLen = base.size() - fillLen;
157+
// Does the end of the input exactly match the fill pattern?
158+
if (base.substr(deltaLen) == *fillIt) {
159+
return padding{1UL, fillLen} + count_padding(base.substr(0, deltaLen), fillStart, fillEnd);
160+
}
127161
}
128162
}
129163

130164
return {};
131165
}
132166

133-
inline std::string encode(const std::string& bin, const std::array<char, 64>& alphabet,
134-
const std::string& fill) {
167+
inline std::string encode(string_view bin, const char* alphabet, string_view fill) {
135168
size_t size = bin.size();
136169
std::string res;
137170

171+
res.reserve((4UL * size) / 3UL);
172+
138173
// clear incomplete bytes
139-
size_t fast_size = size - size % 3;
140-
for (size_t i = 0; i < fast_size;) {
141-
uint32_t octet_a = static_cast<unsigned char>(bin[i++]);
142-
uint32_t octet_b = static_cast<unsigned char>(bin[i++]);
143-
uint32_t octet_c = static_cast<unsigned char>(bin[i++]);
174+
size_t mod = size % 3;
175+
176+
size_t fast_size = size - mod;
177+
for (size_t i = 0; i < fast_size; i += 3) {
178+
uint32_t octet_a = static_cast<unsigned char>(bin[i]);
179+
uint32_t octet_b = static_cast<unsigned char>(bin[i + 1]);
180+
uint32_t octet_c = static_cast<unsigned char>(bin[i + 2]);
144181

145182
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
146183

@@ -152,8 +189,6 @@ namespace jwt {
152189

153190
if (fast_size == size) return res;
154191

155-
size_t mod = size % 3;
156-
157192
uint32_t octet_a = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
158193
uint32_t octet_b = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
159194
uint32_t octet_c = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
@@ -179,9 +214,10 @@ namespace jwt {
179214
return res;
180215
}
181216

182-
inline std::string decode(const std::string& base, const std::array<char, 64>& alphabet,
183-
const std::vector<std::string>& fill) {
184-
const auto pad = count_padding(base, fill);
217+
template<class char_it, class str_input_it>
218+
inline std::string decode(string_view base, char_it alphabetBeg, char_it alphabetEnd,
219+
str_input_it fillStart, str_input_it fillEnd) {
220+
const auto pad = count_padding(base, fillStart, fillEnd);
185221
if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill");
186222

187223
const size_t size = base.size() - pad.length;
@@ -191,7 +227,9 @@ namespace jwt {
191227
std::string res;
192228
res.reserve(out_size);
193229

194-
auto get_sextet = [&](size_t offset) { return alphabet::index(alphabet, base[offset]); };
230+
auto get_sextet = [&](size_t offset) {
231+
return alphabet::index(alphabetBeg, alphabetEnd, base[offset]);
232+
};
195233

196234
size_t fast_size = size - size % 4;
197235
for (size_t i = 0; i < fast_size;) {
@@ -225,46 +263,64 @@ namespace jwt {
225263
return res;
226264
}
227265

228-
inline std::string decode(const std::string& base, const std::array<char, 64>& alphabet,
229-
const std::string& fill) {
230-
return decode(base, alphabet, std::vector<std::string>{fill});
231-
}
232-
233-
inline std::string pad(const std::string& base, const std::string& fill) {
234-
std::string padding;
235-
switch (base.size() % 4) {
236-
case 1: padding += fill; JWT_FALLTHROUGH;
237-
case 2: padding += fill; JWT_FALLTHROUGH;
238-
case 3: padding += fill; JWT_FALLTHROUGH;
266+
inline std::string pad(string_view base, string_view fill) {
267+
std::string res(base);
268+
switch (res.size() % 4) {
269+
case 1: res += fill; JWT_FALLTHROUGH;
270+
case 2: res += fill; JWT_FALLTHROUGH;
271+
case 3: res += fill; JWT_FALLTHROUGH;
239272
default: break;
240273
}
241-
242-
return base + padding;
274+
return res;
243275
}
244276

245-
inline std::string trim(const std::string& base, const std::string& fill) {
277+
inline std::string trim(string_view base, string_view fill) {
246278
auto pos = base.find(fill);
247-
return base.substr(0, pos);
279+
return static_cast<std::string>(base.substr(0, pos));
248280
}
249281
} // namespace details
250282

283+
#ifdef JWT_HAS_STRING_VIEW
251284
template<typename T>
252-
std::string encode(const std::string& bin) {
253-
return details::encode(bin, T::data(), T::fill());
285+
std::string encode(string_view bin) {
286+
return details::encode(bin, T::kData, T::kFill[0]);
254287
}
255288
template<typename T>
256-
std::string decode(const std::string& base) {
257-
return details::decode(base, T::data(), T::fill());
289+
std::string decode(string_view base) {
290+
return details::decode(base, std::begin(T::kData), std::end(T::kData), std::begin(T::kFill),
291+
std::end(T::kFill));
258292
}
259293
template<typename T>
260-
std::string pad(const std::string& base) {
261-
return details::pad(base, T::fill());
294+
std::string pad(string_view base) {
295+
return details::pad(base, T::kFill[0]);
262296
}
263297
template<typename T>
264-
std::string trim(const std::string& base) {
265-
return details::trim(base, T::fill());
298+
std::string trim(string_view base) {
299+
return details::trim(base, T::kFill[0]);
266300
}
301+
302+
#else
303+
template<typename T>
304+
std::string encode(string_view bin) {
305+
return details::encode(bin, T::data().data(), T::fill()[0]);
306+
}
307+
template<typename T>
308+
std::string decode(string_view base) {
309+
return details::decode(base, std::begin(T::data()), std::end(T::data()), std::begin(T::fill()),
310+
std::end(T::fill()));
311+
}
312+
template<typename T>
313+
std::string pad(string_view base) {
314+
return details::pad(base, T::fill()[0]);
315+
}
316+
template<typename T>
317+
std::string trim(string_view base) {
318+
return details::trim(base, T::fill()[0]);
319+
}
320+
#endif
267321
} // namespace base
268322
} // namespace jwt
269323

324+
#undef JWT_BASE_ALPHABET
325+
270326
#endif

0 commit comments

Comments
 (0)