Skip to content

Commit 3570344

Browse files
committed
Addition of string_view support
1 parent f3f091b commit 3570344

File tree

11 files changed

+683
-384
lines changed

11 files changed

+683
-384
lines changed

docs/traits.md

Lines changed: 2 additions & 1 deletion
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

Lines changed: 137 additions & 78 deletions
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,34 @@ 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+
inline uint32_t index(const char* alphabetBeg, const char* alphabetEnd, char symbol) {
121+
if (symbol >= 'A' && symbol <= 'Z') { return static_cast<uint32_t>(symbol - 'A'); }
122+
if (symbol >= 'a' && symbol <= 'z') { return static_cast<uint32_t>(26 + symbol - 'a'); }
123+
if (symbol >= '0' && symbol <= '9') { return static_cast<uint32_t>(52 + symbol - '0'); }
124+
auto itr = std::find(std::next(alphabetBeg, 62U), alphabetEnd, symbol);
125+
if (itr == alphabetEnd) { throw std::runtime_error("Invalid input: not within alphabet"); }
95126

96-
return std::distance(alphabet.cbegin(), itr);
127+
return static_cast<uint32_t>(std::distance(alphabetBeg, itr));
97128
}
98129
} // namespace alphabet
99130

@@ -108,39 +139,48 @@ namespace jwt {
108139
size_t length = 0;
109140

110141
padding() = default;
111-
padding(size_t count, size_t length) : count(count), length(length) {}
112142

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

115147
friend bool operator==(const padding& lhs, const padding& rhs) {
116148
return lhs.count == rhs.count && lhs.length == rhs.length;
117149
}
118150
};
119151

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);
152+
inline std::size_t string_len(string_view str) { return str.size(); }
153+
154+
template<class str_input_it>
155+
padding count_padding(string_view base, str_input_it fillStart, str_input_it fillEnd) {
156+
for (str_input_it fillIt = fillStart; fillIt != fillEnd; ++fillIt) {
157+
std::size_t fillLen = string_len(*fillIt);
158+
if (base.size() >= fillLen) {
159+
std::size_t deltaLen = base.size() - fillLen;
160+
// Does the end of the input exactly match the fill pattern?
161+
if (base.substr(deltaLen) == *fillIt) {
162+
return padding{1UL, fillLen} + count_padding(base.substr(0, deltaLen), fillStart, fillEnd);
163+
}
127164
}
128165
}
129166

130167
return {};
131168
}
132169

133-
inline std::string encode(const std::string& bin, const std::array<char, 64>& alphabet,
134-
const std::string& fill) {
170+
inline std::string encode(string_view bin, const char* alphabet, string_view fill) {
135171
size_t size = bin.size();
136172
std::string res;
137173

174+
res.reserve((4UL * size) / 3UL);
175+
138176
// 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++]);
177+
size_t mod = size % 3;
178+
179+
size_t fast_size = size - mod;
180+
for (size_t i = 0; i < fast_size; i += 3) {
181+
uint32_t octet_a = static_cast<unsigned char>(bin[i]);
182+
uint32_t octet_b = static_cast<unsigned char>(bin[i + 1]);
183+
uint32_t octet_c = static_cast<unsigned char>(bin[i + 2]);
144184

145185
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
146186

@@ -152,8 +192,6 @@ namespace jwt {
152192

153193
if (fast_size == size) return res;
154194

155-
size_t mod = size % 3;
156-
157195
uint32_t octet_a = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
158196
uint32_t octet_b = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
159197
uint32_t octet_c = fast_size < size ? static_cast<unsigned char>(bin[fast_size++]) : 0;
@@ -179,9 +217,10 @@ namespace jwt {
179217
return res;
180218
}
181219

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);
220+
template<class str_input_it>
221+
inline std::string decode(string_view base, const char* alphabetBeg, const char* alphabetEnd,
222+
str_input_it fillStart, str_input_it fillEnd) {
223+
const auto pad = count_padding(base, fillStart, fillEnd);
185224
if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill");
186225

187226
const size_t size = base.size() - pad.length;
@@ -191,7 +230,9 @@ namespace jwt {
191230
std::string res;
192231
res.reserve(out_size);
193232

194-
auto get_sextet = [&](size_t offset) { return alphabet::index(alphabet, base[offset]); };
233+
auto get_sextet = [&](size_t offset) {
234+
return alphabet::index(alphabetBeg, alphabetEnd, base[offset]);
235+
};
195236

196237
size_t fast_size = size - size % 4;
197238
for (size_t i = 0; i < fast_size;) {
@@ -225,46 +266,64 @@ namespace jwt {
225266
return res;
226267
}
227268

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;
269+
inline std::string pad(string_view base, string_view fill) {
270+
std::string res(base);
271+
switch (res.size() % 4) {
272+
case 1: res += fill; JWT_FALLTHROUGH;
273+
case 2: res += fill; JWT_FALLTHROUGH;
274+
case 3: res += fill; JWT_FALLTHROUGH;
239275
default: break;
240276
}
241-
242-
return base + padding;
277+
return res;
243278
}
244279

245-
inline std::string trim(const std::string& base, const std::string& fill) {
280+
inline std::string trim(string_view base, string_view fill) {
246281
auto pos = base.find(fill);
247-
return base.substr(0, pos);
282+
return static_cast<std::string>(base.substr(0, pos));
248283
}
249284
} // namespace details
250285

286+
#ifdef JWT_HAS_STRING_VIEW
251287
template<typename T>
252-
std::string encode(const std::string& bin) {
253-
return details::encode(bin, T::data(), T::fill());
288+
std::string encode(string_view bin) {
289+
return details::encode(bin, T::kData, T::kFill[0]);
254290
}
255291
template<typename T>
256-
std::string decode(const std::string& base) {
257-
return details::decode(base, T::data(), T::fill());
292+
std::string decode(string_view base) {
293+
return details::decode(base, std::begin(T::kData), std::end(T::kData), std::begin(T::kFill),
294+
std::end(T::kFill));
258295
}
259296
template<typename T>
260-
std::string pad(const std::string& base) {
261-
return details::pad(base, T::fill());
297+
std::string pad(string_view base) {
298+
return details::pad(base, T::kFill[0]);
262299
}
263300
template<typename T>
264-
std::string trim(const std::string& base) {
265-
return details::trim(base, T::fill());
301+
std::string trim(string_view base) {
302+
return details::trim(base, T::kFill[0]);
266303
}
304+
305+
#else
306+
template<typename T>
307+
std::string encode(string_view bin) {
308+
return details::encode(bin, T::data().data(), T::fill()[0]);
309+
}
310+
template<typename T>
311+
std::string decode(string_view base) {
312+
return details::decode(base, std::begin(T::data()), std::end(T::data()), std::begin(T::fill()),
313+
std::end(T::fill()));
314+
}
315+
template<typename T>
316+
std::string pad(string_view base) {
317+
return details::pad(base, T::fill()[0]);
318+
}
319+
template<typename T>
320+
std::string trim(string_view base) {
321+
return details::trim(base, T::fill()[0]);
322+
}
323+
#endif
267324
} // namespace base
268325
} // namespace jwt
269326

327+
#undef JWT_BASE_ALPHABET
328+
270329
#endif

0 commit comments

Comments
 (0)