Skip to content

Commit

Permalink
- Changed TokenizedJwt and TokenizedJwe interfaces and implementation…
Browse files Browse the repository at this point in the history
…s to return CharSequences instead of Strings to avoid creating new Strings on the heap

- Changed internal Base64 implementation to work with a CharSequence instead of a raw char[] to reduce need to create new arrays on the heap
- Changed Base64Decoder generics signature from Decoder<String,byte[]> to Decoder<CharSequence,byte[]>
- Decoders.BASE64 and Decoders.BASE64URL now reflect Decoder<CharSequence,byte[]>
- Changed Strings#utf8 implementation to accept a CharSequence instead of a String
- Added new Strings#wrap to wrap a CharSequence into a CharBuffer if necessary
- Replaced not-yet-released JwtBuilder#serializer method with JwtBuilder#jsonWriter
- Replaced not-yet-released JwtParserBuilder#deserializer method with JwtParserBuilder#jsonReader
- Moved JwtDeserializer from io.jsonwebtoken.impl to io.jsonwebtoken.impl.io package, and updated its implementation to work with the new io.jsonwebtoken.io.Writer concept
- Updated GsonSerializer, GsonDeserializer, JacksonSerializer, JacksonDeserializer, and OrgJsonSerializer and OrgJsonDeserializer implementations to use JDK 7+ 'try with resources' try block instead of try/finally
  • Loading branch information
lhazlewood committed Sep 19, 2023
1 parent 5432dcd commit 1bc91d7
Show file tree
Hide file tree
Showing 47 changed files with 524 additions and 449 deletions.
9 changes: 5 additions & 4 deletions api/src/main/java/io/jsonwebtoken/Claims.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.jsonwebtoken;

import io.jsonwebtoken.io.Reader;

import java.util.Date;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -147,10 +149,9 @@ public interface Claims extends Map<String, Object>, Identifiable {
* Returns the JWTs claim ({@code claimName}) value as a type {@code requiredType}, or {@code null} if not present.
*
* <p>JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more
* complex is expected to be already converted to your desired type by the JSON
* {@link io.jsonwebtoken.io.Deserializer Deserializer} implementation. You may specify a custom Deserializer for a
* JwtParser with the desired conversion configuration via the
* {@link JwtParserBuilder#deserializer deserializer} method.
* complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom
* JSON {@link io.jsonwebtoken.io.Reader Reader} with the desired configuration via the
* {@link JwtParserBuilder#jsonReader(Reader)} method.
* See <a href="https://github.com/jwtk/jjwt#custom-json-processor">custom JSON processor</a> for more
* information. If using Jackson, you can specify custom claim POJO types as described in
* <a href="https://github.com/jwtk/jjwt#json-jackson-custom-types">custom claim types</a>.
Expand Down
16 changes: 2 additions & 14 deletions api/src/main/java/io/jsonwebtoken/JwtBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @deprecated since JJWT_RELEASE_VERSION in favor of the more modern builder-style
* {@link #encoder(Encoder)} method.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder);

Expand Down Expand Up @@ -888,6 +889,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @since 0.10.0
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #jsonWriter(Writer)}
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
JwtBuilder serializeToJsonWith(Serializer<Map<String, ?>> serializer);

Expand All @@ -905,20 +907,6 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*/
JwtBuilder jsonWriter(Writer<Map<String, ?>> writer);

/**
* Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert
* JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification.
*
* <p>If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found
* in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.</p>
*
* @param serializer the serializer to use when converting Map objects to JSON strings.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder serializer(Serializer<Map<String, ?>> serializer);

/**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
* <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-7.1">JWT Compact Serialization</a>
Expand Down
20 changes: 11 additions & 9 deletions api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.io.Reader;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
Expand Down Expand Up @@ -710,7 +711,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
* @param base64UrlDecoder the decoder to use when Base64Url-decoding
* @return the parser builder for method chaining.
*/
JwtParserBuilder decoder(Decoder<String, byte[]> base64UrlDecoder);
JwtParserBuilder decoder(Decoder<CharSequence, byte[]> base64UrlDecoder);

/**
* Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is
Expand All @@ -724,26 +725,27 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
*
* @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects.
* @return the builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of the shorter and more modern builder-style named
* {@link #deserializer(Deserializer)}. This method will be removed before the JJWT 1.0 release.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #jsonReader(Reader)}.
* This method will be removed before the JJWT 1.0 release.
*/
@Deprecated
JwtParserBuilder deserializeJsonWith(Deserializer<Map<String, ?>> deserializer);

/**
* Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is
* used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map
* objects.
* Uses the specified JSON {@link Reader} to deserialize JSON (UTF-8 byte streams) into Java Map objects. This is
* used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map
* instances.
*
* <p>If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the
* <p>If this method is not called, JJWT will use whatever Reader it can find at runtime, checking for the
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found
* in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is
* invoked.</p>
*
* @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects.
* @param reader the reader to use to deserialize JSON (UTF-8 byte streams) into Map instances.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder deserializer(Deserializer<Map<String, ?>> deserializer);
JwtParserBuilder jsonReader(Reader<Map<String, ?>> reader);

/**
* Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder.
Expand Down
29 changes: 15 additions & 14 deletions api/src/main/java/io/jsonwebtoken/io/Base64.java
Original file line number Diff line number Diff line change
Expand Up @@ -224,40 +224,41 @@ private int ctoi(char c) {
}

/**
* Decodes a BASE64 encoded char array that is known to be reasonably well formatted. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions
* are:<br>
* + The sequence must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
* + The sequence must not contain illegal characters within the encoded string<br>
* + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @param seq The source sequence. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
* @throws DecodingException on illegal input
*/
final byte[] decodeFast(char[] sArr) throws DecodingException {
byte[] decodeFast(CharSequence seq) throws DecodingException {

// Check special case
int sLen = sArr != null ? sArr.length : 0;
int sLen = seq != null ? seq.length() : 0;
if (sLen == 0) {
return new byte[0];
}

int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.

// Trim illegal chars from start
while (sIx < eIx && IALPHABET[sArr[sIx]] < 0) {
while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) {
sIx++;
}

// Trim illegal chars from end
while (eIx > 0 && IALPHABET[sArr[eIx]] < 0) {
while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) {
eIx--;
}

// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;

int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
Expand All @@ -267,7 +268,7 @@ final byte[] decodeFast(char[] sArr) throws DecodingException {
for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {

// Assemble three bytes into an int from four "valid" characters.
int i = ctoi(sArr[sIx++]) << 18 | ctoi(sArr[sIx++]) << 12 | ctoi(sArr[sIx++]) << 6 | ctoi(sArr[sIx++]);
int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++));

// Add the bytes
dArr[d++] = (byte) (i >> 16);
Expand All @@ -285,7 +286,7 @@ final byte[] decodeFast(char[] sArr) throws DecodingException {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= ctoi(sArr[sIx++]) << (18 - j * 6);
i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6);
}

for (int r = 16; d < len; r -= 8) {
Expand Down Expand Up @@ -533,7 +534,7 @@ public final byte[] decodeFast(byte[] sArr) {
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
final String encodeToString(byte[] sArr, boolean lineSep) {
String encodeToString(byte[] sArr, boolean lineSep) {
// Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
return new String(encodeToChar(sArr, lineSep));
}
Expand Down
6 changes: 3 additions & 3 deletions api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
* @since 0.10.0
*/
class Base64Decoder extends Base64Support implements Decoder<String, byte[]> {
class Base64Decoder extends Base64Support implements Decoder<CharSequence, byte[]> {

Base64Decoder() {
super(Base64.DEFAULT);
Expand All @@ -34,8 +34,8 @@ class Base64Decoder extends Base64Support implements Decoder<String, byte[]> {
}

@Override
public byte[] decode(String s) throws DecodingException {
public byte[] decode(CharSequence s) throws DecodingException {
Assert.notNull(s, "String argument cannot be null");
return this.base64.decodeFast(s.toCharArray());
return this.base64.decodeFast(s);
}
}
4 changes: 2 additions & 2 deletions api/src/main/java/io/jsonwebtoken/io/Decoders.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ public final class Decoders {
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">Base64</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Decoder<String, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());
public static final Decoder<CharSequence, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());

/**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">Base64Url</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Decoder<String, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());
public static final Decoder<CharSequence, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());

private Decoders() { //prevent instantiation
}
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/java/io/jsonwebtoken/lang/Assert.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public static void hasLength(String text) {
* @return the string if it has text
* @see Strings#hasText
*/
public static String hasText(String text, String message) {
public static <T extends CharSequence> T hasText(T text, String message) {
if (!Strings.hasText(text)) {
throw new IllegalArgumentException(message);
}
Expand Down
33 changes: 25 additions & 8 deletions api/src/main/java/io/jsonwebtoken/lang/Strings.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.jsonwebtoken.lang;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand All @@ -41,6 +43,8 @@ public final class Strings {
*/
public static final String EMPTY = "";

private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY);

private static final String FOLDER_SEPARATOR = "/";

private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
Expand Down Expand Up @@ -238,10 +242,13 @@ public static CharSequence clean(CharSequence str) {
* @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}.
* @since JJWT_RELEASE_VERSION
*/
public static byte[] utf8(String s) {
public static byte[] utf8(CharSequence s) {
byte[] bytes = null;
if (s != null) {
bytes = s.getBytes(UTF_8);
CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s);
ByteBuffer buf = UTF_8.encode(cb);
bytes = new byte[buf.remaining()];
buf.get(bytes);
}
return bytes;
}
Expand All @@ -257,6 +264,19 @@ public static String utf8(byte[] utf8Bytes) {
return new String(utf8Bytes, UTF_8);
}

/**
* Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If
* {@code seq} is already a {@code CharBuffer}, it is returned unmodified.
*
* @param seq the {@code CharSequence} to wrap.
* @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null.
*/
public static CharBuffer wrap(CharSequence seq) {
if (!hasLength(seq)) return EMPTY_BUF;
if (seq instanceof CharBuffer) return (CharBuffer) seq;
return CharBuffer.wrap(seq);
}

/**
* Returns a String representation (1s and 0s) of the specified byte.
*
Expand Down Expand Up @@ -823,8 +843,7 @@ private static void validateLocalePart(String localePart) {
for (int i = 0; i < localePart.length(); i++) {
char ch = localePart.charAt(i);
if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) {
throw new IllegalArgumentException(
"Locale part \"" + localePart + "\" contains invalid characters");
throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters");
}
}
}
Expand Down Expand Up @@ -1049,8 +1068,7 @@ public static Properties splitArrayElementsIntoProperties(String[] array, String
* @return a <code>Properties</code> instance representing the array contents,
* or <code>null</code> if the array to process was <code>null</code> or empty
*/
public static Properties splitArrayElementsIntoProperties(
String[] array, String delimiter, String charsToDelete) {
public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) {

if (Objects.isEmpty(array)) {
return null;
Expand Down Expand Up @@ -1109,8 +1127,7 @@ public static String[] tokenizeToStringArray(String str, String delimiters) {
* @see java.lang.String#trim()
* @see #delimitedListToStringArray
*/
public static String[] tokenizeToStringArray(
String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

if (str == null) {
return null;
Expand Down
Loading

0 comments on commit 1bc91d7

Please sign in to comment.