From 8119fd2ad1e2d009991f9a12969ccd55054163c4 Mon Sep 17 00:00:00 2001
From: haby0 The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
+there is a problem of sensitive information leakage. The function name verification processing for external input can effectively prevent the leakage of sensitive information. The following example shows the case of no verification processing and verification processing for the external input function name. The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
+there is a problem of sensitive information leakage. Adding `Referer` or random `token` verification processing can effectively prevent the leakage of sensitive information. The following example shows the case of no verification processing and verification processing for the external input function name. The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
-there is a problem of sensitive information leakage. The function name verification processing for external input can effectively prevent the leakage of sensitive information. The following example shows the case of no verification processing and verification processing for the external input function name.
Ensure that a modern, strong protocol is used. All versions of SSL,
- and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
- above is strongly recommended.
+ and TLS versions 1.0 and 1.1 are known to be vulnerable to attacks.
+ Using TLS 1.2 or above is strongly recommended.
All cases should be updated to use a secure protocol, such as
-
Note that The following example shows the case of no verification processing and verification processing for the external input function name.PROTOCOL_TLSv1_1
.
+ PROTOCOL_TLSv1_2
.
ssl.wrap_socket
has been deprecated in
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/ReadMe.md
new file mode 100644
index 000000000000..c5330a78fda9
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/ReadMe.md
@@ -0,0 +1,24 @@
+# Current status (Feb 2021)
+
+This should be kept up to date; the world is moving fast and protocols are being broken.
+
+## Protocols
+
+- All versions of SSL are insecure
+- TLS 1.0 and TLS 1.1 are insecure
+- TLS 1.2 have some issues. but TLS 1.3 is not widely supported
+
+## Conection methods
+
+- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
+ > Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
+- Default consteructors are fine, a sluent api is used to constrain possible protocols later.
+
+## Current recomendation
+
+TLS 1.2 or TLS 1.3
+
+## Queries
+
+- `InsecureProtocol` detects uses of insecure protocols.
+- `InsecureDefaultProtocol` detect default constructions, this is no longer unsafe.
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index 5a6d044aa6de..cb21a6623c9e 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -1,5 +1,5 @@
import ssl
-from pyOpenSSL import SSL
+from OpenSSL import SSL
from ssl import SSLContext
# true positives
@@ -33,9 +33,9 @@
# secure versions
-ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_1)
-SSLContext(protocol=ssl.PROTOCOL_TLSv1_1)
-SSL.Context(SSL.TLSv1_1_METHOD)
+ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
+SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
+SSL.Context(SSL.TLSv1_2_METHOD)
# possibly insecure default
ssl.wrap_socket()
From 3b856010f28ade83481b3e9fe553c4bb56f0f432 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
+ Note that ssl.wrap_socket
has been deprecated in
+ Python 3.7. The recommended alternatives are:
+
ssl.SSLContext
- supported in Python 2.7.9,
+ 3.2, and later versionsssl.create_default_context
- a convenience function,
+ supported in Python 3.4 and later versions.+ Even when you use these alternatives, you should + ensure that a safe protocol is used. The following code illustrates + how to use flags (available since Python 3.2) or the `minimum_version` + field (favored since Python 3.7) to restrict the protocols accepted when + creating a connection. +
+ +The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem, -there is a problem of sensitive information leakage.
+The software uses external input as the function name to wrap JSON data and returns it to the client as a request response. +When there is a cross-domain problem, the problem of sensitive information leakage may occur.
Adding `Referer` or random `token` verification processing can effectively prevent the leakage of sensitive information.
+Adding Referer
/Origin
or random token
verification processing can effectively prevent the leakage of sensitive information.
The following example shows the case of no verification processing and verification processing for the external input function name.
+The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad7
,
+will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
+method and the good2
method, use the verifToken
method to do the random token
Verification can
+solve the problem of information leakage caused by cross-domain.
+Jakarta Expression Language (EL) is an expression language for Java applications. +There are a single language specification and multiple implementations +such as Glassfish, Juel, Apache Commons EL, etc. +The language allows invocation of methods available in the JVM. +If an expression is built using attacker-controlled data, +and then evaluated, then it may allow the attacker to run arbitrary code. +
++It is generally recommended to avoid using untrusted data in an EL expression. +Before using untrusted data to build an EL expressoin, the data should be validated +to ensure it is not evaluated as expression language. If the EL implementaion offers +configuring a sandbox for EL expression, they should be run in a restircitive sandbox +that allows accessing only explicitly allowed classes. If the EL implementation +does not allow sandboxing, consider using other expressiong language implementations +with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language. +
++The following example shows how untrusted data is used to build and run an expression +using the JUEL interpreter: +
++JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code, +incoming data has to be checked before including to an expression. The next example +uses a Regex pattern to check whether a user tries to run an allowed exression or not: +
+PROTOCOL_TLSv1_2
.
-
- Note that ssl.wrap_socket
has been deprecated in
- Python 3.7. A preferred alternative is to use
- ssl.SSLContext
, which is supported in Python 2.7.9 and
- 3.2 and later versions.
-
Note that If an LDAP query is built by a not sanitized user-provided value, a user is likely to be able to run malicious LDAP queries. In case user input must compose an LDAP query, it should be escaped in order to avoid a malicious user supplying special characters that change the actual purpose of the query. To do so, functions that ldap frameworks provide such as ssl.wrap_socket
has been deprecated in
Python 3.7. The recommended alternatives are:
From bf81122fc6b26584e85d4f0b7b4f854a2296471b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen escape_filter_chars
should be applied to that user input.
+
The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad7
,
+
The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad8
,
will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
method and the good2
method, use the verifToken
method to do the random token
Verification can
solve the problem of information leakage caused by cross-domain.
This is ultimately a JSON map and any values can be added to it, but JWT standard names are provided as + * type-safe getters and setters for convenience.
+ * + *Because this interface extends {@code Map<String, Object>}, if you would like to add your own properties, + * you simply use map methods, for example:
+ * + *+ * claims.{@link Map#put(Object, Object) put}("someKey", "someValue"); + *+ * + *
It is easiest to create a {@code Claims} instance by calling one of the + * {@link Jwts#claims() JWTs.claims()} factory methods.
+ * + * @since 0.1 + */ +public interface Claims extends Map"iss"
*/
+ public static final String ISSUER = "iss";
+
+ /** JWT {@code Subject} claims parameter name: "sub"
*/
+ public static final String SUBJECT = "sub";
+
+ /** JWT {@code Audience} claims parameter name: "aud"
*/
+ public static final String AUDIENCE = "aud";
+
+ /** JWT {@code Expiration} claims parameter name: "exp"
*/
+ public static final String EXPIRATION = "exp";
+
+ /** JWT {@code Not Before} claims parameter name: "nbf"
*/
+ public static final String NOT_BEFORE = "nbf";
+
+ /** JWT {@code Issued At} claims parameter name: "iat"
*/
+ public static final String ISSUED_AT = "iat";
+
+ /** JWT {@code JWT ID} claims parameter name: "jti"
*/
+ public static final String ID = "jti";
+
+ /**
+ * Returns the JWT
+ * iss
(issuer) value or {@code null} if not present.
+ *
+ * @return the JWT {@code iss} value or {@code null} if not present.
+ */
+ String getIssuer();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override //only for better/targeted JavaDoc
+ Claims setIssuer(String iss);
+
+ /**
+ * Returns the JWT
+ * sub
(subject) value or {@code null} if not present.
+ *
+ * @return the JWT {@code sub} value or {@code null} if not present.
+ */
+ String getSubject();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override //only for better/targeted JavaDoc
+ Claims setSubject(String sub);
+
+ /**
+ * Returns the JWT
+ * aud
(audience) value or {@code null} if not present.
+ *
+ * @return the JWT {@code aud} value or {@code null} if not present.
+ */
+ String getAudience();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override //only for better/targeted JavaDoc
+ Claims setAudience(String aud);
+
+ /**
+ * Returns the JWT
+ * exp
(expiration) timestamp or {@code null} if not present.
+ *
+ * A JWT obtained after this timestamp should not be used.
+ * + * @return the JWT {@code exp} value or {@code null} if not present. + */ + Date getExpiration(); + + /** + * {@inheritDoc} + */ + @Override //only for better/targeted JavaDoc + Claims setExpiration(Date exp); + + /** + * Returns the JWT + *nbf
(not before) timestamp or {@code null} if not present.
+ *
+ * A JWT obtained before this timestamp should not be used.
+ * + * @return the JWT {@code nbf} value or {@code null} if not present. + */ + Date getNotBefore(); + + /** + * {@inheritDoc} + */ + @Override //only for better/targeted JavaDoc + Claims setNotBefore(Date nbf); + + /** + * Returns the JWT + *iat
(issued at) timestamp or {@code null} if not present.
+ *
+ * If present, this value is the timestamp when the JWT was created.
+ * + * @return the JWT {@code iat} value or {@code null} if not present. + */ + Date getIssuedAt(); + + /** + * {@inheritDoc} + */ + @Override //only for better/targeted JavaDoc + Claims setIssuedAt(Date iat); + + /** + * Returns the JWTs + *jti
(JWT ID) value or {@code null} if not present.
+ *
+ * This value is a CaSe-SenSiTiVe unique identifier for the JWT. If available, this value is expected to be + * assigned in a manner that ensures that there is a negligible probability that the same value will be + * accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.
+ * + * @return the JWT {@code jti} value or {@code null} if not present. + */ + String getId(); + + /** + * {@inheritDoc} + */ + @Override //only for better/targeted JavaDoc + Claims setId(String jti); + + /** + * Returns the JWTs claim ({@code claimName}) value as a type {@code requiredType}, or {@code null} if not present. + * + *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#deserializeJsonWith} method.
+ * See custom JSON processor for more
+ * information. If using Jackson, you can specify custom claim POJO types as described in
+ * custom claim types.
+ *
+ * @param claimName name of claim
+ * @param requiredType the type of the value expected to be returned
+ * @param A JWT obtained after this timestamp should not be used. A JWT obtained before this timestamp should not be used. The value is the timestamp when the JWT was created. This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a
+ * manner that ensures that there is a negligible probability that the same value will be accidentally
+ * assigned to a different data object. The ID can be used to prevent the JWT from being replayed. JJWT's default {@link JwtParser} implementation supports both the
+ * {@link CompressionCodecs#DEFLATE DEFLATE}
+ * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to
+ * specify a {@code CompressionCodecResolver} in these cases. However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
+ * your own {@link CompressionCodecResolver} and specify that when
+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
+ * {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs. This is not a standard JWA compression algorithm. Be sure to use this only when you are confident
+ * that all parties accessing the token support the gzip algorithm. If you're concerned about compatibility, the {@link #DEFLATE DEFLATE} code is JWA standards-compliant. This is ultimately a JSON map and any values can be added to it, but JWT JOSE standard names are provided as
+ * type-safe getters and setters for convenience. Because this interface extends {@code Map<String, Object>}, if you would like to add your own properties,
+ * you simply use map methods, for example: It is easiest to create a {@code Header} instance by calling one of the
+ * {@link Jwts#header() JWTs.header()} factory methods. In the normal case where nested signing or encryption operations are not employed (i.e. a compact
+ * serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested
+ * signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be
+ * {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not
+ * case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for
+ * compatibility with legacy implementations. See
+ * JWT Appendix A.2 for
+ * an example of a Nested JWT. In the normal case where nested signing or encryption operations are not employed (i.e. a compact
+ * serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested
+ * signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be
+ * {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not
+ * case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for
+ * compatibility with legacy implementations. See
+ * JWT Appendix A.2 for
+ * an example of a Nested JWT.
+ * The compression algorithm is NOT part of the JWT specification
+ * and must be used carefully since, is not expected that other libraries (including previous versions of this one)
+ * be able to deserialize a compressed JTW body correctly. The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
+ * using {@link io.jsonwebtoken.SignatureAlgorithm#forName(String) SignatureAlgorithm.forName} to convert this
+ * string value to a type-safe enum instance. The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
+ * using a type-safe {@link io.jsonwebtoken.SignatureAlgorithm SignatureAlgorithm} instance and using its
+ * {@link io.jsonwebtoken.SignatureAlgorithm#getValue() value} as the argument to this method. The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
+ * originators to explicitly signal a change of key to recipients. The structure of the keyId value is
+ * unspecified. When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value. The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
+ * originators to explicitly signal a change of key to recipients. The structure of the keyId value is
+ * unspecified. When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value. The payload and claims properties are mutually exclusive - only one of the two may be used. The payload and claims properties are mutually exclusive - only one of the two may be used. The payload* and claims* properties are mutually exclusive - only one of the two may be used. The payload and claims properties are mutually exclusive - only one of the two may be used. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setIssuer(String) issuer} field with the specified value. This allows you to write
+ * code like this: instead of this: if desired. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setSubject(String) subject} field with the specified value. This allows you to write
+ * code like this: instead of this: if desired. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setAudience(String) audience} field with the specified value. This allows you to write
+ * code like this: instead of this: if desired. A JWT obtained after this timestamp should not be used. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setExpiration(java.util.Date) expiration} field with the specified value. This allows
+ * you to write code like this: instead of this: if desired. A JWT obtained before this timestamp should not be used. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setNotBefore(java.util.Date) notBefore} field with the specified value. This allows
+ * you to write code like this: instead of this: if desired. The value is the timestamp when the JWT was created. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setIssuedAt(java.util.Date) issuedAt} field with the specified value. This allows
+ * you to write code like this: instead of this: if desired. The value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a
+ * manner that ensures that there is a negligible probability that the same value will be accidentally
+ * assigned to a different data object. The ID can be used to prevent the JWT from being replayed. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set
+ * the Claims {@link Claims#setId(String) id} field with the specified value. This allows
+ * you to write code like this: instead of this: if desired. This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set the
+ * named property on the Claims instance using the Claims {@link Claims#put(Object, Object) put} method. This allows
+ * you to write code like this: instead of this: if desired. If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA
+ * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
+ * convert the byte array into a valid {@code Key}. Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
+ * obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}. This method will be removed in the 1.0 release. This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
+ * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}. This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
+ * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
+ * obtained from the String argument. This method always expected a String argument that was effectively the same as the result of the following
+ * (pseudocode): {@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);} However, a non-trivial number of JJWT users were confused by the method signature and attempted to
+ * use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is
+ * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results. See this
+ *
+ * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for
+ * signature operations. To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:
+ * iss
(issuer) value. A {@code null} value will remove the property from the JSON map.
+ *
+ * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map.
+ * @return the {@code Claims} instance for method chaining.
+ */
+ T setIssuer(String iss);
+
+ /**
+ * Sets the JWT
+ * sub
(subject) value. A {@code null} value will remove the property from the JSON map.
+ *
+ * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map.
+ * @return the {@code Claims} instance for method chaining.
+ */
+ T setSubject(String sub);
+
+ /**
+ * Sets the JWT
+ * aud
(audience) value. A {@code null} value will remove the property from the JSON map.
+ *
+ * @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map.
+ * @return the {@code Claims} instance for method chaining.
+ */
+ T setAudience(String aud);
+
+ /**
+ * Sets the JWT
+ * exp
(expiration) timestamp. A {@code null} value will remove the property from the JSON map.
+ *
+ * nbf
(not before) timestamp. A {@code null} value will remove the property from the JSON map.
+ *
+ * iat
(issued at) timestamp. A {@code null} value will remove the property from the JSON map.
+ *
+ * jti
(JWT ID) value. A {@code null} value will remove the property from the JSON map.
+ *
+ * Compatibility Warning
+ *
+ * header.{@link Map#put(Object, Object) put}("headerParamName", "headerParamValue");
+ *
+ *
+ * Creation
+ *
+ * "JWT"
*/
+ public static final String JWT_TYPE = "JWT";
+
+ /** JWT {@code Type} header parameter name: "typ"
*/
+ public static final String TYPE = "typ";
+
+ /** JWT {@code Content Type} header parameter name: "cty"
*/
+ public static final String CONTENT_TYPE = "cty";
+
+ /** JWT {@code Compression Algorithm} header parameter name: "zip"
*/
+ public static final String COMPRESSION_ALGORITHM = "zip";
+
+ /** JJWT legacy/deprecated compression algorithm header parameter name: "calg"
+ * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */
+ @Deprecated
+ public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
+
+ /**
+ * Returns the
+ * typ
(type) header value or {@code null} if not present.
+ *
+ * @return the {@code typ} header value or {@code null} if not present.
+ */
+ String getType();
+
+ /**
+ * Sets the JWT
+ * typ
(Type) header value. A {@code null} value will remove the property from the JSON map.
+ *
+ * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map.
+ * @return the {@code Header} instance for method chaining.
+ */
+ T setType(String typ);
+
+ /**
+ * Returns the
+ * cty
(Content Type) header value or {@code null} if not present.
+ *
+ * cty
(Content Type) header parameter value. A {@code null} value will remove the property from
+ * the JSON map.
+ *
+ * zip
(Compression Algorithm) header value or {@code null} if not present.
+ *
+ * @return the {@code zip} header parameter value or {@code null} if not present.
+ * @since 0.6.0
+ */
+ String getCompressionAlgorithm();
+
+ /**
+ * Sets the JWT zip
(Compression Algorithm) header parameter value. A {@code null} value will remove
+ * the property from the JSON map.
+ * "alg"
*/
+ public static final String ALGORITHM = "alg";
+
+ /** JWS {@code JWT Set URL} header parameter name: "jku"
*/
+ public static final String JWK_SET_URL = "jku";
+
+ /** JWS {@code JSON Web Key} header parameter name: "jwk"
*/
+ public static final String JSON_WEB_KEY = "jwk";
+
+ /** JWS {@code Key ID} header parameter name: "kid"
*/
+ public static final String KEY_ID = "kid";
+
+ /** JWS {@code X.509 URL} header parameter name: "x5u"
*/
+ public static final String X509_URL = "x5u";
+
+ /** JWS {@code X.509 Certificate Chain} header parameter name: "x5c"
*/
+ public static final String X509_CERT_CHAIN = "x5c";
+
+ /** JWS {@code X.509 Certificate SHA-1 Thumbprint} header parameter name: "x5t"
*/
+ public static final String X509_CERT_SHA1_THUMBPRINT = "x5t";
+
+ /** JWS {@code X.509 Certificate SHA-256 Thumbprint} header parameter name: "x5t#S256"
*/
+ public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
+
+ /** JWS {@code Critical} header parameter name: "crit"
*/
+ public static final String CRITICAL = "crit";
+
+ /**
+ * Returns the JWS
+ * alg
(algorithm) header value or {@code null} if not present.
+ *
+ * alg
(Algorithm) header value. A {@code null} value will remove the property from the JSON map.
+ *
+ * kid
(Key ID) header value or {@code null} if not present.
+ *
+ * kid
(Key ID) header value. A {@code null} value will remove the property from the JSON map.
+ *
+ * iss
(issuer) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setIssuer("Joe").compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setIssuer("Joe");
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * sub
(subject) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setSubject("Me").compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setSubject("Me");
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * aud
(audience) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setAudience("You").compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setAudience("You");
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * exp
(expiration) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setExpiration(new Date(System.currentTimeMillis() + 3600000)).compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setExpiration(new Date(System.currentTimeMillis() + 3600000));
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * nbf
(not before) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setNotBefore(new Date()).compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setNotBefore(new Date());
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * iat
(issued at) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setIssuedAt(new Date()).compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setIssuedAt(new Date());
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * jti
(JWT ID) value. A {@code null} value will remove the property from the Claims.
+ *
+ *
+ * String jwt = Jwts.builder().setId(UUID.randomUUID().toString()).compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().setId(UUID.randomUUID().toString());
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ *
+ * String jwt = Jwts.builder().claim("aName", "aValue").compact();
+ *
+ *
+ *
+ * Claims claims = Jwts.claims().put("aName", "aValue");
+ * String jwt = Jwts.builder().setClaims(claims).compact();
+ *
+ * Deprecation Notice: Deprecated as of 0.10.0
+ *
+ * Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.
+ *
+ *
+ *
+ * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
+ * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
+ * jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
+ *
This method will be removed in the 1.0 release.
+ * + * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. + * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the + * JWT. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as + * described by {@link SignatureAlgorithm#forSigningKey(Key)}. + * @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead. This + * method will be removed in the 1.0 release. + */ + @Deprecated + JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException; + + /** + * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. + * + *It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.
+ * + * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. + * @param key the algorithm-specific signing key to use to digitally sign the JWT. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @deprecated since 0.10.0: use {@link #signWith(Key, SignatureAlgorithm)} instead. This method will be removed + * in the 1.0 release. + */ + @Deprecated + JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; + + /** + * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. + * + *It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.
+ * + * @param key the signing key to use to digitally sign the JWT. + * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @since 0.10.0 + */ + JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; + + /** + * Compresses the JWT body using the specified {@link CompressionCodec}. + * + *If your compact JWTs are large, and you want to reduce their total size during network transmission, this + * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a + * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:
+ * + *The JWT family of specifications defines compression only for JWE (Json Web Encryption) + * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. + * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to + * parse that JWS token. When using compression for JWS tokens, be sure that that all parties accessing the + * JWS token support compression for JWS.
+ * + *Compression when creating JWE tokens however should be universally accepted for any + * library that supports JWE.
+ * + * @param codec implementation of the {@link CompressionCodec} to be used. + * @return the builder for method chaining. + * @see io.jsonwebtoken.CompressionCodecs + * @since 0.6.0 + */ + JwtBuilder compressWith(CompressionCodec codec); + + /** + * Perform Base64Url encoding with the specified Encoder. + * + *JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method + * to specify a different encoder if you desire.
+ * + * @param base64UrlEncoder the encoder to use when Base64Url-encoding + * @return the builder for method chaining. + * @since 0.10.0 + */ + JwtBuilder base64UrlEncodeWith(EncoderIf 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.
+ * + * @param serializer the serializer to use when converting Map objects to JSON strings. + * @return the builder for method chaining. + * @since 0.10.0 + */ + JwtBuilder serializeToJsonWith(SerializerThis method will only be invoked if the cryptographic signature can be successfully verified.
+ * + * @param jws the parsed plaintext JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T onPlaintextJws(JwsThis method will only be invoked if the cryptographic signature can be successfully verified.
+ * + * @param jws the parsed claims JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T onClaimsJws(JwsAll of the methods in this implementation throw exceptions: overridden methods represent + * scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did + * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT + * input was unexpected.
+ * + * @paramNOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireId(String id); + + /** + * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param subject + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireSubject(String)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireSubject(String subject); + + /** + * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param audience + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireAudience(String)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireAudience(String audience); + + /** + * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param issuer + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireIssuer(String)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireIssuer(String issuer); + + /** + * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param issuedAt + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireIssuedAt(Date)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireIssuedAt(Date issuedAt); + + /** + * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param expiration + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireExpiration(Date)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireExpiration(Date expiration); + + /** + * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param notBefore + * @return the parser for method chaining + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#requireNotBefore(Date)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser requireNotBefore(Date notBefore); + + /** + * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed + * value does not equal the specified value, an exception will be thrown indicating that the + * JWT is invalid and may not be used. + * + * @param claimName + * @param value + * @return the parser for method chaining. + * @see MissingClaimException + * @see IncorrectClaimException + * @deprecated see {@link JwtParserBuilder#require(String, Object)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser require(String claimName, Object value); + + /** + * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. + * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. + * + * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. + * @return the parser for method chaining. + * @since 0.7.0 + * @deprecated see {@link JwtParserBuilder#setClock(Clock)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setClock(Clock clock); + + /** + * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} + * and {@code nbf} claims. + * + * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. + * @return the parser for method chaining. + * @since 0.7.0 + * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as + * any such value would cause numeric overflow when multiplying by 1000 to obtain a millisecond value. + * @deprecated see {@link JwtParserBuilder#setAllowedClockSkewSeconds(long)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *
NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; + + /** + * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not + * a JWS (no signature), this key is not used. + *
+ *
Note that this key MUST be a valid key for the signature algorithm found in the JWT header + * (as the {@code alg} header parameter).
+ *+ *
This method overwrites any previously set key.
+ * + * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital + * signature. + * @return the parser for method chaining. + * @deprecated see {@link JwtParserBuilder#setSigningKey(byte[])}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setSigningKey(byte[] key); + + /** + * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not + * a JWS (no signature), this key is not used. + * + *
Note that this key MUST be a valid key for the signature algorithm found in the JWT header + * (as the {@code alg} header parameter).
+ * + *This method overwrites any previously set key.
+ * + *This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting + * byte array is used to invoke {@link #setSigningKey(byte[])}.
+ * + *This method has been deprecated because the {@code key} argument for this method can be confusing: keys for + * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were + * obtained from the String argument.
+ * + *This method always expected a String argument that was effectively the same as the result of the following + * (pseudocode):
+ * + *{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}
+ * + *However, a non-trivial number of JJWT users were confused by the method signature and attempted to + * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is + * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.
+ * + *See this + * + * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for + * signature operations.
+ * + *Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the + * {@code byte[]} variant will be removed before the 1.0.0 release.
+ * + * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate + * any discovered JWS digital signature. + * @return the parser for method chaining. + * @deprecated see {@link JwtParserBuilder#setSigningKey(String)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setSigningKey(String base64EncodedSecretKey); + + /** + * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not + * a JWS (no signature), this key is not used. + *
+ *
Note that this key MUST be a valid key for the signature algorithm found in the JWT header + * (as the {@code alg} header parameter).
+ *+ *
This method overwrites any previously set key.
+ * + * @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital + * signature. + * @return the parser for method chaining. + * @deprecated see {@link JwtParserBuilder#setSigningKey(Key)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0
+ */
+ @Deprecated
+ JwtParser setSigningKey(Key key);
+
+ /**
+ * Sets the {@link SigningKeyResolver} used to acquire the signing key
that should be used to verify
+ * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
+ *
+ *
Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing + * the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to + * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the + * returned key. For example:
+ *+ *
+ * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() { + * @Override + * public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + * //inspect the header or claims, lookup and return the signing key + * return getSigningKey(header, claims); //implement me + * }}) + * .parseClaimsJws(compact); + *+ *
+ *
A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.
+ *+ *
This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder + * methods.
+ * + * @param signingKeyResolver the signing key resolver used to retrieve the signing key. + * @return the parser for method chaining. + * @since 0.4 + * @deprecated see {@link JwtParserBuilder#setSigningKeyResolver(SigningKeyResolver)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver); + + /** + * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to + * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. + *
NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries + * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only + * useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements + * the same behavior.
+ *JJWT's default {@link JwtParser} implementation supports both the + * {@link CompressionCodecs#DEFLATE DEFLATE} + * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to + * specify a {@code CompressionCodecResolver} in these cases.
+ *However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement + * your own {@link CompressionCodecResolver} and specify that via this method and also when + * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.
+ * + * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. + * @return the parser for method chaining. + * @since 0.6.0 + * @deprecated see {@link JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0 + */ + @Deprecated + JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); + + /** + * Perform Base64Url decoding with the specified Decoder + * + *
JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method + * to specify a different decoder if you desire.
+ * + * @param base64UrlDecoder the decoder to use when Base64Url-decoding + * @return the parser for method chaining. + * @since 0.10.0 + * @deprecated see {@link JwtParserBuilder#base64UrlDecodeWith(Decoder)}. + * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an + * immutable JwtParser. + *NOTE: this method will be removed before version 1.0
+ */
+ @Deprecated
+ JwtParser base64UrlDecodeWith(Decoder If this method is not called, JJWT will use whatever deserializer 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. NOTE: this method will be removed before version 1.0
+ */
+ @Deprecated
+ JwtParser deserializeJsonWith(Deserializer
+ * Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
+ * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.
+ * This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it
+ * is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the
+ * {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that
+ * may help reduce code or instanceof checks.
+ * If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
+ * {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
+ * for your use case(s), for example:
+ *
+ * If you know the JWT string can be only one type of JWT, then it is even easier to invoke one of the
+ * following convenience methods instead of this one:
+ *
+ * This is a convenience method that is usable if you are confident that the compact string argument reflects an
+ * unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
+ * cryptographically signed.
+ * If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body,
+ * an {@link UnsupportedJwtException} will be thrown.
+ * This is a convenience method that is usable if you are confident that the compact string argument reflects an
+ * unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
+ * signed.
+ * If the compact string presented does not reflect an unsigned Claims JWT, an
+ * {@link UnsupportedJwtException} will be thrown.
+ * This is a convenience method that is usable if you are confident that the compact string argument reflects a
+ * plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
+ * cryptographically signed.
+ * If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
+ * will be thrown.
+ * This is a convenience method that is usable if you are confident that the compact string argument reflects a
+ * Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.
+ * If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
+ * thrown.
+ * Note that this key MUST be a valid key for the signature algorithm found in the JWT header
+ * (as the {@code alg} header parameter).
+ * This method overwrites any previously set key. Note that this key MUST be a valid key for the signature algorithm found in the JWT header
+ * (as the {@code alg} header parameter). This method overwrites any previously set key. This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
+ * byte array is used to invoke {@link #setSigningKey(byte[])}. This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
+ * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
+ * obtained from the String argument. This method always expected a String argument that was effectively the same as the result of the following
+ * (pseudocode): {@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);} However, a non-trivial number of JJWT users were confused by the method signature and attempted to
+ * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is
+ * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results. See this
+ *
+ * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for
+ * signature operations. Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the
+ * {@code byte[]} variant will be removed before the 1.0.0 release.
+ * Note that this key MUST be a valid key for the signature algorithm found in the JWT header
+ * (as the {@code alg} header parameter).
+ * This method overwrites any previously set key.
+ * Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
+ * the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to
+ * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
+ * returned key. For example:
+ *
+ * A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.
+ * This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
+ * methods. NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries
+ * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only
+ * useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements
+ * the same behavior. JJWT's default {@link JwtParser} implementation supports both the
+ * {@link CompressionCodecs#DEFLATE DEFLATE}
+ * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to
+ * specify a {@code CompressionCodecResolver} in these cases. However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
+ * your own {@link CompressionCodecResolver} and specify that via this method and also when
+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs. JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method
+ * to specify a different decoder if you desire. If this method is not called, JJWT will use whatever deserializer 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. Migration to new method structure is minimal, for example:
+ * Old code:
+ * New code:
+ * NOTE: this method will be removed before version 1.0
+ */
+ @Deprecated
+ public static JwtParser parser() {
+ return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParser");
+ }
+
+ /**
+ * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser).
+ *
+ * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser).
+ */
+ public static JwtParserBuilder parserBuilder() {
+ return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder");
+ }
+
+ /**
+ * Returns a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized
+ * strings.
+ *
+ * @return a new {@link JwtBuilder} instance that can be configured and then used to create JWT compact serialized
+ * strings.
+ */
+ public static JwtBuilder builder() {
+ return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder");
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MalformedJwtException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MalformedJwtException.java
new file mode 100644
index 000000000000..6490550aaf7e
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MalformedJwtException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception indicating that a JWT was not correctly constructed and should be rejected.
+ *
+ * @since 0.2
+ */
+public class MalformedJwtException extends JwtException {
+
+ public MalformedJwtException(String message) {
+ super(message);
+ }
+
+ public MalformedJwtException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MissingClaimException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MissingClaimException.java
new file mode 100644
index 000000000000..030fe98d06b7
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/MissingClaimException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception thrown when discovering that a required claim is not present, indicating the JWT is
+ * invalid and may not be used.
+ *
+ * @since 0.6
+ */
+public class MissingClaimException extends InvalidClaimException {
+ public MissingClaimException(Header header, Claims claims, String message) {
+ super(header, claims, message);
+ }
+
+ public MissingClaimException(Header header, Claims claims, String message, Throwable cause) {
+ super(header, claims, message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/PrematureJwtException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/PrematureJwtException.java
new file mode 100644
index 000000000000..8853832e80ae
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/PrematureJwtException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception indicating that a JWT was accepted before it is allowed to be accessed and must be rejected.
+ *
+ * @since 0.3
+ */
+public class PrematureJwtException extends ClaimJwtException {
+
+ public PrematureJwtException(Header header, Claims claims, String message) {
+ super(header, claims, message);
+ }
+
+ /**
+ * @param header jwt header
+ * @param claims jwt claims (body)
+ * @param message exception message
+ * @param cause cause
+ * @since 0.5
+ */
+ public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) {
+ super(header, claims, message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/RequiredTypeException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/RequiredTypeException.java
new file mode 100644
index 000000000000..eeb60d3084d1
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/RequiredTypeException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the
+ * {@code Class} argument.
+ *
+ * @since 0.6
+ */
+public class RequiredTypeException extends JwtException {
+ public RequiredTypeException(String message) {
+ super(message);
+ }
+
+ public RequiredTypeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/SignatureAlgorithm.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/SignatureAlgorithm.java
new file mode 100644
index 000000000000..9f646502dfa1
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/SignatureAlgorithm.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+import io.jsonwebtoken.security.InvalidKeyException;
+import io.jsonwebtoken.security.Keys;
+import io.jsonwebtoken.security.SignatureException;
+import io.jsonwebtoken.security.WeakKeyException;
+
+import javax.crypto.SecretKey;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Type-safe representation of standard JWT signature algorithm names as defined in the
+ * JSON Web Algorithms specification.
+ *
+ * @since 0.1
+ */
+public enum SignatureAlgorithm {
+
+ /**
+ * JWA name for {@code No digital signature or MAC performed}
+ */
+ NONE("none", "No digital signature or MAC performed", "None", null, false, 0, 0),
+
+ /**
+ * JWA algorithm name for {@code HMAC using SHA-256}
+ */
+ HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"),
+
+ /**
+ * JWA algorithm name for {@code HMAC using SHA-384}
+ */
+ HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"),
+
+ /**
+ * JWA algorithm name for {@code HMAC using SHA-512}
+ */
+ HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11"),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256}
+ */
+ RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384}
+ */
+ RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512}
+ */
+ RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048),
+
+ /**
+ * JWA algorithm name for {@code ECDSA using P-256 and SHA-256}
+ */
+ ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256),
+
+ /**
+ * JWA algorithm name for {@code ECDSA using P-384 and SHA-384}
+ */
+ ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384),
+
+ /**
+ * JWA algorithm name for {@code ECDSA using P-521 and SHA-512}
+ */
+ ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. This algorithm requires
+ * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or
+ * earlier, BouncyCastle will be used automatically if found in the runtime classpath.
+ */
+ PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "RSASSA-PSS", false, 256, 2048),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. This algorithm requires
+ * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or
+ * earlier, BouncyCastle will be used automatically if found in the runtime classpath.
+ */
+ PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "RSASSA-PSS", false, 384, 2048),
+
+ /**
+ * JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. This algorithm requires
+ * Java 11 or later or a JCA provider like BouncyCastle to be in the runtime classpath. If on Java 10 or
+ * earlier, BouncyCastle will be used automatically if found in the runtime classpath.
+ */
+ PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "RSASSA-PSS", false, 512, 2048);
+
+ //purposefully ordered higher to lower:
+ private static final List Notes: This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variant for any
+ * specified {@link RSAKey} because:
+ * Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the
+ * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or
+ * explicitly disallowed by the JWT specification. A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the
+ * JWT header or payload (plaintext body or Claims) must be inspected first to determine how to look up the signing key.
+ * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For
+ * example: A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified. If you only need to resolve a signing key for a particular JWS (either a plaintext or Claims JWS), consider using
+ * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of
+ * implementing this interface directly. The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, String)} method
+ * implementations delegate to the
+ * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, String)} methods
+ * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by
+ * calling code in known situations, and it is expected that you override the implementation in those known situations;
+ * non-overridden *KeyBytes methods indicates that the JWS input was unexpected. If either {@link #resolveSigningKey(JwsHeader, String)} or {@link #resolveSigningKey(JwsHeader, Claims)}
+ * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected
+ * use case. You do not have to override any method that does not represent an expected condition. NOTE: You cannot override this method when validating RSA signatures. If you expect RSA signatures,
+ * you must override the {@link #resolveSigningKey(JwsHeader, Claims)} method instead. For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application
+ * requires a cryptographically signed Claims JWS instead. Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting. This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
+ * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
+ * as large as algorithms that create a temporary array. There is also a "fast" version of all decode methods that works the same way as the normal ones, but
+ * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if
+ * the input is known and it hasn't bee tampered with. Call {@link #isTrue(boolean)} if you wish to
+ * throw {@link IllegalArgumentException} on an assertion failure.
+ * A Uses Enforces the given instance to be present, rather than returning
+ * Typical usage: Typical usage: A Compares arrays with Differs from {@link #nullSafeToString(Object)} in that it returns
+ * an empty String rather than "null" for a Returns Builds a String representation of the contents in case of an array.
+ * Returns The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ( The result is convenient for path comparison. For other uses,
+ * notice that Windows separators ("\") are replaced by simple slashes.
+ * @param path the original path
+ * @return the normalized path
+ */
+ public static String cleanPath(String path) {
+ if (path == null) {
+ return null;
+ }
+ String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
+
+ // Strip prefix from path to analyze, to not treat it as part of the
+ // first path element. This is necessary to correctly parse paths like
+ // "file:core/../core/io/Resource.class", where the ".." should just
+ // strip the first "core" directory while keeping the "file:" prefix.
+ int prefixIndex = pathToUse.indexOf(":");
+ String prefix = "";
+ if (prefixIndex != -1) {
+ prefix = pathToUse.substring(0, prefixIndex + 1);
+ pathToUse = pathToUse.substring(prefixIndex + 1);
+ }
+ if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
+ prefix = prefix + FOLDER_SEPARATOR;
+ pathToUse = pathToUse.substring(1);
+ }
+
+ String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
+ List This is the inverse operation of {@link java.util.Locale#toString Locale's toString}.
+ * @param localeString the locale string, following The order of elements in the original arrays is preserved.
+ * @param array1 the first array (can be The order of elements in the original arrays is preserved
+ * (with the exception of overlapping elements, which are only
+ * included on their first occurrence).
+ * @param array1 the first array (can be Will trim both the key and value before adding them to the
+ * Will trim both the key and value before adding them to the
+ * The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to JWA Specification (RFC 7518), Section 3.2
+ * requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a
+ * secure-random generated SecretKey that adheres to the required minimum key length. The lengths are: If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following: If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following: In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources. These code snippets look suspicious and require the developer's attention. We recommend that you use more explicit code transformations. The following example demonstrates the erroneous and corrected sections of the code.
Jakarta Expression Language (EL) is an expression language for Java applications.
-There are a single language specification and multiple implementations
+There is a single language specification and multiple implementations
such as Glassfish, Juel, Apache Commons EL, etc.
The language allows invocation of methods available in the JVM.
If an expression is built using attacker-controlled data,
-and then evaluated, then it may allow the attacker to run arbitrary code.
+and then evaluated, it may allow the attacker to run arbitrary code.
It is generally recommended to avoid using untrusted data in an EL expression.
-Before using untrusted data to build an EL expressoin, the data should be validated
-to ensure it is not evaluated as expression language. If the EL implementaion offers
-configuring a sandbox for EL expression, they should be run in a restircitive sandbox
+Before using untrusted data to build an EL expression, the data should be validated
+to ensure it is not evaluated as expression language. If the EL implementation offers
+configuring a sandbox for EL expressions, they should be run in a restrictive sandbox
that allows accessing only explicitly allowed classes. If the EL implementation
-does not allow sandboxing, consider using other expressiong language implementations
+does not support sandboxing, consider using other expression language implementations
with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
-JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
-incoming data has to be checked before including to an expression. The next example
-uses a Regex pattern to check whether a user tries to run an allowed exression or not:
+JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code,
+incoming data has to be checked before including it in an expression. The next example
+uses a Regex pattern to check whether a user tries to run an allowed expression or not:
The software obtains the original client IP address through the http header When the software is not using a proxy server, get the last ip. The following examples show the bad case and the good case respectively. Bad case, such as
+ * String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
+ *
+ * String subject = Jwts.parser().setSigningKey(key).parse(compactJwt, new JwtHandlerAdapter<String>() {
+ * @Override
+ * public String onClaimsJws(Jws<Claims> jws) {
+ * return jws.getBody().getSubject();
+ * }
+ * });
+ *
+ *
+ *
+ *
+ * @param jwt the compact serialized JWT to parse
+ * @return the result returned by the {@code JwtHandler}
+ * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
+ * Invalid JWTs should not be trusted and should be discarded.
+ * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
+ * signature validation should not be trusted and should be discarded.
+ * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
+ * before the time this method is invoked.
+ * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
+ * {@code handler} is {@code null}.
+ * @see #parsePlaintextJwt(String)
+ * @see #parseClaimsJwt(String)
+ * @see #parsePlaintextJws(String)
+ * @see #parseClaimsJws(String)
+ * @see #parse(String)
+ * @since 0.2
+ */
+ {@code
+ * Jwts.parserBuilder()
+ * .setSigningKey(...)
+ * .requireIssuer("https://issuer.example.com")
+ * .build()
+ * .parse(jwtString)
+ * }
+ * @since 0.11.0
+ */
+public interface JwtParserBuilder {
+
+ /**
+ * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param id
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireId(String id);
+
+ /**
+ * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param subject
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireSubject(String subject);
+
+ /**
+ * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param audience
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireAudience(String audience);
+
+ /**
+ * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param issuer
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireIssuer(String issuer);
+
+ /**
+ * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param issuedAt
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireIssuedAt(Date issuedAt);
+
+ /**
+ * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param expiration
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireExpiration(Date expiration);
+
+ /**
+ * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param notBefore
+ * @return the parser builder for method chaining
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder requireNotBefore(Date notBefore);
+
+ /**
+ * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param claimName
+ * @param value
+ * @return the parser builder for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParserBuilder require(String claimName, Object value);
+
+ /**
+ * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
+ * The parser uses a default Clock implementation that simply returns {@code new Date()} when called.
+ *
+ * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
+ * @return the parser builder for method chaining.
+ */
+ JwtParserBuilder setClock(Clock clock);
+
+ /**
+ * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp}
+ * and {@code nbf} claims.
+ *
+ * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims.
+ * @return the parser builder for method chaining.
+ * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as
+ * any such value would cause numeric overflow when multiplying by 1000 to obtain a millisecond value.
+ */
+ JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException;
+
+ /**
+ * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
+ * a JWS (no signature), this key is not used.
+ * Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0
+ *
+ * signing key
that should be used to verify
+ * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
+ *
+ * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+ * @Override
+ * public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+ * //inspect the header or claims, lookup and return the signing key
+ * return getSigningKey(header, claims); //implement me
+ * }})
+ * .parseClaimsJws(compact);
+ *
+ * Default Support
+ * {@code
+ * Jwts.parser()
+ * .requireAudience("string")
+ * .parse(jwtString)
+ * }
+ * {@code
+ * Jwts.parserBuilder()
+ * .requireAudience("string")
+ * .build()
+ * .parse(jwtString)
+ * }
+ *
+ *
+ *
+ * @return Returns the cryptographic family name of the signature algorithm.
+ * @since 0.5
+ */
+ public String getFamilyName() {
+ return familyName;
+ }
+
+ /**
+ * Returns the name of the JCA algorithm used to compute the signature.
+ *
+ * @return the name of the JCA algorithm used to compute the signature.
+ */
+ public String getJcaName() {
+ return jcaName;
+ }
+
+ /**
+ * Returns {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the
+ * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like
+ * BouncyCastle for example).
+ *
+ * @return {@code true} if the algorithm is supported by standard JDK distributions or {@code false} if the
+ * algorithm implementation is not in the JDK and must be provided by a separate runtime JCA Provider (like
+ * BouncyCastle for example).
+ */
+ public boolean isJdkStandard() {
+ return jdkStandard;
+ }
+
+ /**
+ * Returns {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise.
+ *
+ * @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise.
+ */
+ public boolean isHmac() {
+ return familyName.equals("HMAC");
+ }
+
+ /**
+ * Returns {@code true} if the enum instance represents an RSA public/private key pair signature algorithm,
+ * {@code false} otherwise.
+ *
+ * @return {@code true} if the enum instance represents an RSA public/private key pair signature algorithm,
+ * {@code false} otherwise.
+ */
+ public boolean isRsa() {
+ return familyName.equals("RSA");
+ }
+
+ /**
+ * Returns {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false}
+ * otherwise.
+ *
+ * @return {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false}
+ * otherwise.
+ */
+ public boolean isEllipticCurve() {
+ return familyName.equals("ECDSA");
+ }
+
+ /**
+ * Returns the minimum key length in bits (not bytes) that may be used with this algorithm according to the
+ * JWT JWA Specification (RFC 7518).
+ *
+ * @return the minimum key length in bits (not bytes) that may be used with this algorithm according to the
+ * JWT JWA Specification (RFC 7518).
+ * @since 0.10.0
+ */
+ public int getMinKeyLength() {
+ return this.minKeyLength;
+ }
+
+ /**
+ * Returns quietly if the specified key is allowed to create signatures using this algorithm
+ * according to the JWT JWA Specification (RFC 7518) or throws an
+ * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
+ *
+ * @param key the key to check for validity.
+ * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
+ * @since 0.10.0
+ */
+ public void assertValidSigningKey(Key key) throws InvalidKeyException {
+ assertValid(key, true);
+ }
+
+ /**
+ * Returns quietly if the specified key is allowed to verify signatures using this algorithm
+ * according to the JWT JWA Specification (RFC 7518) or throws an
+ * {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
+ *
+ * @param key the key to check for validity.
+ * @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
+ * @since 0.10.0
+ */
+ public void assertValidVerificationKey(Key key) throws InvalidKeyException {
+ assertValid(key, false);
+ }
+
+ /**
+ * @since 0.10.0 to support assertValid(Key, boolean)
+ */
+ private static String keyType(boolean signing) {
+ return signing ? "signing" : "verification";
+ }
+
+ /**
+ * @since 0.10.0
+ */
+ private void assertValid(Key key, boolean signing) throws InvalidKeyException {
+
+ if (this == NONE) {
+
+ String msg = "The 'NONE' signature algorithm does not support cryptographic keys.";
+ throw new InvalidKeyException(msg);
+
+ } else if (isHmac()) {
+
+ if (!(key instanceof SecretKey)) {
+ String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances.";
+ throw new InvalidKeyException(msg);
+ }
+ SecretKey secretKey = (SecretKey) key;
+
+ byte[] encoded = secretKey.getEncoded();
+ if (encoded == null) {
+ throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null.");
+ }
+
+ String alg = secretKey.getAlgorithm();
+ if (alg == null) {
+ throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null.");
+ }
+
+ // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272
+ if (!HS256.jcaName.equalsIgnoreCase(alg) &&
+ !HS384.jcaName.equalsIgnoreCase(alg) &&
+ !HS512.jcaName.equalsIgnoreCase(alg) &&
+ !HS256.pkcs12Name.equals(alg) &&
+ !HS384.pkcs12Name.equals(alg) &&
+ !HS512.pkcs12Name.equals(alg)) {
+ throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg +
+ "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + ".");
+ }
+
+ int size = encoded.length * 8; //size in bits
+ if (size < this.minKeyLength) {
+ String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " +
+ "is not secure enough for the " + name() + " algorithm. The JWT " +
+ "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " +
+ "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " +
+ "output size). Consider using the " + Keys.class.getName() + " class's " +
+ "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " +
+ "secure enough for " + name() + ". See " +
+ "https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
+ throw new WeakKeyException(msg);
+ }
+
+ } else { //EC or RSA
+
+ if (signing) {
+ if (!(key instanceof PrivateKey)) {
+ String msg = familyName + " signing keys must be PrivateKey instances.";
+ throw new InvalidKeyException(msg);
+ }
+ }
+
+ if (isEllipticCurve()) {
+
+ if (!(key instanceof ECKey)) {
+ String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances.";
+ throw new InvalidKeyException(msg);
+ }
+
+ ECKey ecKey = (ECKey) key;
+ int size = ecKey.getParams().getOrder().bitLength();
+ if (size < this.minKeyLength) {
+ String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size +
+ " bits which is not secure enough for the " + name() + " algorithm. The JWT " +
+ "JWA Specification (RFC 7518, Section 3.4) states that keys used with " +
+ name() + " MUST have a size >= " + this.minKeyLength +
+ " bits. Consider using the " + Keys.class.getName() + " class's " +
+ "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
+ "to be secure enough for " + name() + ". See " +
+ "https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
+ throw new WeakKeyException(msg);
+ }
+
+ } else { //RSA
+
+ if (!(key instanceof RSAKey)) {
+ String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances.";
+ throw new InvalidKeyException(msg);
+ }
+
+ RSAKey rsaKey = (RSAKey) key;
+ int size = rsaKey.getModulus().bitLength();
+ if (size < this.minKeyLength) {
+
+ String section = name().startsWith("P") ? "3.5" : "3.3";
+
+ String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " +
+ "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " +
+ section + ") states that keys used with " + name() + " MUST have a size >= " +
+ this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " +
+ "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
+ "to be secure enough for " + name() + ". See " +
+ "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information.";
+ throw new WeakKeyException(msg);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the recommended signature algorithm to be used with the specified key according to the following
+ * heuristics:
+ *
+ *
+ *
+ *
+ *
+ * SignatureAlgorithm
+ * Family Name
+ *
+ *
+ * HS256
+ * HMAC
+ *
+ *
+ * HS384
+ * HMAC
+ *
+ *
+ * HS512
+ * HMAC
+ *
+ *
+ * RS256
+ * RSA
+ *
+ *
+ * RS384
+ * RSA
+ *
+ *
+ * RS512
+ * RSA
+ *
+ *
+ * PS256
+ * RSA
+ *
+ *
+ * PS384
+ * RSA
+ *
+ *
+ * PS512
+ * RSA
+ *
+ *
+ * ES256
+ * ECDSA
+ *
+ *
+ * ES384
+ * ECDSA
+ *
+ *
+ *
+ * ES512
+ * ECDSA
+ *
+ *
+ *
+ *
+ *
+ *
+ * If the Key is a:
+ * And:
+ * With a key size of:
+ * The returned SignatureAlgorithm will be:
+ *
+ *
+ * {@link SecretKey}
+ *
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")
1256 <= size <= 383 2
+ * {@link SignatureAlgorithm#HS256 HS256}
+ *
+ *
+ * {@link SecretKey}
+ *
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")
1384 <= size <= 511
+ * {@link SignatureAlgorithm#HS384 HS384}
+ *
+ *
+ * {@link SecretKey}
+ *
+ * {@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")
1512 <= size
+ * {@link SignatureAlgorithm#HS512 HS512}
+ *
+ *
+ * {@link ECKey}
+ *
+ * instanceof {@link PrivateKey}
256 <= size <= 383 3
+ * {@link SignatureAlgorithm#ES256 ES256}
+ *
+ *
+ * {@link ECKey}
+ *
+ * instanceof {@link PrivateKey}
384 <= size <= 511
+ * {@link SignatureAlgorithm#ES384 ES384}
+ *
+ *
+ * {@link ECKey}
+ *
+ * instanceof {@link PrivateKey}
4096 <= size
+ * {@link SignatureAlgorithm#ES512 ES512}
+ *
+ *
+ * {@link RSAKey}
+ *
+ * instanceof {@link PrivateKey}
2048 <= size <= 3071 4,5
+ * {@link SignatureAlgorithm#RS256 RS256}
+ *
+ *
+ * {@link RSAKey}
+ *
+ * instanceof {@link PrivateKey}
3072 <= size <= 4095 5
+ * {@link SignatureAlgorithm#RS384 RS384}
+ *
+ *
+ *
+ * {@link RSAKey}
+ *
+ * instanceof {@link PrivateKey}
4096 <= size 5
+ * {@link SignatureAlgorithm#RS512 RS512}
+ *
+ *
+ *
+ *
+ *
+ * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
+ * @Override
+ * public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
+ * //inspect the header or claims, lookup and return the signing key
+ * return getSigningKeyBytes(header, claims); //implement me
+ * }})
+ * .parseClaimsJws(compact);
+ *
+ *
+ * SigningKeyResolverAdapter
+ *
+ * char[]
representation in accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ */
+ private char[] encodeToChar(byte[] sArr, boolean lineSep) {
+
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0) {
+ return new char[0];
+ }
+
+ int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks
+ int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2
+
+ int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators
+
+ int padCount = 0;
+ if (left == 2) {
+ padCount = 1;
+ } else if (left == 1) {
+ padCount = 2;
+ }
+
+ char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen];
+
+ // Encode even 24-bits
+ for (int s = 0, d = 0, cc = 0; s < eLen; ) {
+
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = ALPHABET[(i >>> 18) & 0x3f];
+ dArr[d++] = ALPHABET[(i >>> 12) & 0x3f];
+ dArr[d++] = ALPHABET[(i >>> 6) & 0x3f];
+ dArr[d++] = ALPHABET[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't even 24 bits.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = ALPHABET[i >> 12];
+ dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f];
+ //dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '=';
+ //dArr[dLen - 1] = '=';
+ if (left == 2) {
+ dArr[dLen - 2] = ALPHABET[i & 0x3f];
+ } else if (!urlsafe) { // if not urlsafe, we need to include the padding characters
+ dArr[dLen - 2] = '=';
+ }
+ if (!urlsafe) { // include padding
+ dArr[dLen - 1] = '=';
+ }
+ }
+ return dArr;
+ }
+
+ /*
+ * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. null
or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(char[] sArr) {
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[sArr[i]] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) {
+ if (sArr[i] == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[sArr[s++]];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+ return dArr;
+ }
+ */
+
+ private int ctoi(char c) {
+ int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c];
+ if (i < 0) {
+ String msg = "Illegal " + getName() + " character: '" + c + "'";
+ throw new DecodingException(msg);
+ }
+ return i;
+ }
+
+ /**
+ * Decodes a BASE64 encoded char array that is known to be reasonably well formatted. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
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 {
+
+ // Check special case
+ int sLen = sArr != null ? sArr.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) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[sArr[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 cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (sArr[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
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ 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++]);
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // 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);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+
+ // ****************************************************************************************
+ // * byte[] version
+ // ****************************************************************************************
+
+ /*
+ * Encodes a raw byte array into a BASE64 byte[]
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ *
+ public final byte[] encodeToByte(byte[] sArr, boolean lineSep) {
+ return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep);
+ }
+
+ /**
+ * Encodes a raw byte array into a BASE64 byte[]
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
an empty array will be returned.
+ * @param sOff The starting position in the bytes to convert.
+ * @param sLen The number of bytes to convert. If 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ *
+ public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) {
+
+ // Check special case
+ if (sArr == null || sLen == 0) {
+ return new byte[0];
+ }
+
+ int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
+ byte[] dArr = new byte[dLen];
+
+ // Encode even 24-bits
+ for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) {
+
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't an even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = (byte) ALPHABET[i >> 12];
+ dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f];
+ dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '=';
+ dArr[dLen - 1] = '=';
+ }
+ return dArr;
+ }
+
+ /**
+ * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(byte[] sArr) {
+ return decode(sArr, 0, sArr.length);
+ }
+
+ /**
+ * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. null
will throw an exception.
+ * @param sOff The starting position in the source array.
+ * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(byte[] sArr, int sOff, int sLen) {
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[sArr[sOff + i] & 0xff] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) {
+ if (sArr[sOff + i] == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[sArr[sOff + s++] & 0xff];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+
+ return dArr;
+ }
+
+
+ /*
+ * Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ *
+ public final byte[] decodeFast(byte[] sArr) {
+
+ // Check special case
+ int sLen = sArr.length;
+ 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] & 0xff] < 0) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) {
+ eIx--;
+ }
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = sArr[eIx] == '=' ? (sArr[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 len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
+
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++) {
+ i |= IALPHABET[sArr[sIx++]] << (18 - j * 6);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+ */
+
+ // ****************************************************************************************
+ // * String version
+ // ****************************************************************************************
+
+ /**
+ * Encodes a raw byte array into a BASE64 String
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ */
+ final 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));
+ }
+
+ /*
+ * Decodes a BASE64 encoded String
. All illegal characters will be ignored and can handle both strings with
+ * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray())
instead. That
+ * will create a temporary array though. This version will use str.charAt(i)
to iterate the string.
+ *
+ * @param str The source string. null
or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(String str) {
+
+ // Check special case
+ int sLen = str != null ? str.length() : 0;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[str.charAt(i)] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ // Count '=' at end
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) {
+ if (str.charAt(i) == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[str.charAt(s++)];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+ return dArr;
+ }
+
+ /**
+ * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045
+ * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ *
+ * @param s The source string. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ *
+ public final byte[] decodeFast(String s) {
+
+ // Check special case
+ int sLen = s.length();
+ 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[s.charAt(sIx) & 0xff] < 0) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) {
+ eIx--;
+ }
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (s.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
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++) {
+ i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+ */
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/io/Base64Decoder.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/io/Base64Decoder.java
new file mode 100644
index 000000000000..3c8bc817daaf
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/io/Base64Decoder.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.io;
+
+import io.jsonwebtoken.lang.Assert;
+
+/**
+ * @since 0.10.0
+ */
+class Base64Decoder extends Base64Support implements DecoderIllegalArgumentException
+ * if the test result is false
.
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing IllegalArgumentException
+ * if the test result is false
.
+ * Assert.isTrue(i > 0);
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression) {
+ isTrue(expression, "[Assertion failed] - this expression must be true");
+ }
+
+ /**
+ * Assert that an object is null
.
+ * Assert.isNull(value, "The value must be null");
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is not null
+ */
+ public static void isNull(Object object, String message) {
+ if (object != null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is null
.
+ * Assert.isNull(value);
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is not null
+ */
+ public static void isNull(Object object) {
+ isNull(object, "[Assertion failed] - the object argument must be null");
+ }
+
+ /**
+ * Assert that an object is not null
.
+ * Assert.notNull(clazz, "The class must not be null");
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is null
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is not null
.
+ * Assert.notNull(clazz);
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is null
+ */
+ public static void notNull(Object object) {
+ notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be null
and not the empty String.
+ * Assert.hasLength(name, "Name must not be empty");
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see Strings#hasLength
+ */
+ public static void hasLength(String text, String message) {
+ if (!Strings.hasLength(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be null
and not the empty String.
+ * Assert.hasLength(name);
+ * @param text the String to check
+ * @see Strings#hasLength
+ */
+ public static void hasLength(String text) {
+ hasLength(text,
+ "[Assertion failed] - this String argument must have length; it must not be null or empty");
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be null
and must contain at least one non-whitespace character.
+ * Assert.hasText(name, "'name' must not be empty");
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see Strings#hasText
+ */
+ public static void hasText(String text, String message) {
+ if (!Strings.hasText(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be null
and must contain at least one non-whitespace character.
+ * Assert.hasText(name, "'name' must not be empty");
+ * @param text the String to check
+ * @see Strings#hasText
+ */
+ public static void hasText(String text) {
+ hasText(text,
+ "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ * @param message the exception message to use if the assertion fails
+ */
+ public static void doesNotContain(String textToSearch, String substring, String message) {
+ if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) &&
+ textToSearch.indexOf(substring) != -1) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * Assert.doesNotContain(name, "rod");
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ */
+ public static void doesNotContain(String textToSearch, String substring) {
+ doesNotContain(textToSearch, substring,
+ "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+ }
+
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * null
and must have at least one element.
+ * Assert.notEmpty(array, "The array must have elements");
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array is null
or has no elements
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (Objects.isEmpty(array)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * null
and must have at least one element.
+ * Assert.notEmpty(array);
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array is null
or has no elements
+ */
+ public static void notEmpty(Object[] array) {
+ notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+ }
+
+ public static void notEmpty(byte[] array, String msg) {
+ if (Objects.isEmpty(array)) {
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * Assert.noNullElements(array, "The array must have non-null elements");
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array contains a null
element
+ */
+ public static void noNullElements(Object[] array, String message) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * Assert.noNullElements(array);
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array contains a null
element
+ */
+ public static void noNullElements(Object[] array) {
+ noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * null
and must have at least one element.
+ * Assert.notEmpty(collection, "Collection must have elements");
+ * @param collection the collection to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the collection is null
or has no elements
+ */
+ public static void notEmpty(Collection collection, String message) {
+ if (Collections.isEmpty(collection)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * null
and must have at least one element.
+ * Assert.notEmpty(collection, "Collection must have elements");
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is null
or has no elements
+ */
+ public static void notEmpty(Collection collection) {
+ notEmpty(collection,
+ "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be null
+ * and must have at least one entry.
+ * Assert.notEmpty(map, "Map must have entries");
+ * @param map the map to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the map is null
or has no entries
+ */
+ public static void notEmpty(Map map, String message) {
+ if (Collections.isEmpty(map)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be null
+ * and must have at least one entry.
+ * Assert.notEmpty(map);
+ * @param map the map to check
+ * @throws IllegalArgumentException if the map is null
or has no entries
+ */
+ public static void notEmpty(Map map) {
+ notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+ }
+
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * Assert.instanceOf(Foo.class, foo);
+ * @param clazz the required class
+ * @param obj the object to check
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class clazz, Object obj) {
+ isInstanceOf(clazz, obj, "");
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * Assert.instanceOf(Foo.class, foo);
+ * @param type the type to check against
+ * @param obj the object to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class type, Object obj, String message) {
+ notNull(type, "Type to check against must not be null");
+ if (!type.isInstance(obj)) {
+ throw new IllegalArgumentException(message +
+ "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+ "] must be an instance of " + type);
+ }
+ }
+
+ /**
+ * Assert that superType.isAssignableFrom(subType)
is true
.
+ * Assert.isAssignable(Number.class, myClass);
+ * @param superType the super type to check
+ * @param subType the sub type to check
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType) {
+ isAssignable(superType, subType, "");
+ }
+
+ /**
+ * Assert that superType.isAssignableFrom(subType)
is true
.
+ * Assert.isAssignable(Number.class, myClass);
+ * @param superType the super type to check against
+ * @param subType the sub type to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType, String message) {
+ notNull(superType, "Type to check against must not be null");
+ if (subType == null || !superType.isAssignableFrom(subType)) {
+ throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+ }
+ }
+
+
+ /**
+ * Assert a boolean expression, throwing IllegalStateException
+ * if the test result is false
. Call isTrue if you wish to
+ * throw IllegalArgumentException on an assertion failure.
+ * Assert.state(id == null, "The id property must not already be initialized");
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalStateException if expression is false
+ */
+ public static void state(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing {@link IllegalStateException}
+ * if the test result is false
.
+ * Assert.state(id == null);
+ * @param expression a boolean expression
+ * @throws IllegalStateException if the supplied expression is false
+ */
+ public static void state(boolean expression) {
+ state(expression, "[Assertion failed] - this state invariant must be true");
+ }
+
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Classes.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Classes.java
new file mode 100644
index 000000000000..27d4d18c2fe8
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Classes.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.lang;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * @since 0.1
+ */
+public final class Classes {
+
+ private Classes() {} //prevent instantiation
+
+ /**
+ * @since 0.1
+ */
+ private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+ @Override
+ protected ClassLoader doGetClassLoader() throws Throwable {
+ return Thread.currentThread().getContextClassLoader();
+ }
+ };
+
+ /**
+ * @since 0.1
+ */
+ private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+ @Override
+ protected ClassLoader doGetClassLoader() throws Throwable {
+ return Classes.class.getClassLoader();
+ }
+ };
+
+ /**
+ * @since 0.1
+ */
+ private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
+ @Override
+ protected ClassLoader doGetClassLoader() throws Throwable {
+ return ClassLoader.getSystemClassLoader();
+ }
+ };
+
+ /**
+ * Attempts to load the specified class name from the current thread's
+ * {@link Thread#getContextClassLoader() context class loader}, then the
+ * current ClassLoader (Classes.class.getClassLoader()
), then the system/application
+ * ClassLoader (ClassLoader.getSystemClassLoader()
, in that order. If any of them cannot locate
+ * the specified class, an UnknownClassException
is thrown (our RuntimeException equivalent of
+ * the JRE's ClassNotFoundException
.
+ *
+ * @param fqcn the fully qualified class name to load
+ * @return the located class
+ * @throws UnknownClassException if the class cannot be found.
+ */
+ @SuppressWarnings("unchecked")
+ public static Classes.class.getClassLoader()
), then the system/application
+ * ClassLoader (ClassLoader.getSystemClassLoader()
, in that order, using
+ * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}.
+ *
+ * @param name the name of the resource to acquire from the classloader(s).
+ * @return the InputStream of the resource found, or null
if the resource cannot be found from any
+ * of the three mentioned ClassLoaders.
+ * @since 0.8
+ */
+ public static InputStream getResourceAsStream(String name) {
+
+ InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
+
+ if (is == null) {
+ is = CLASS_CL_ACCESSOR.getResourceStream(name);
+ }
+
+ if (is == null) {
+ is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
+ }
+
+ return is;
+ }
+
+ public static boolean isAvailable(String fullyQualifiedClassName) {
+ try {
+ forName(fullyQualifiedClassName);
+ return true;
+ } catch (UnknownClassException e) {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static true
if the supplied Collection is null
+ * or empty. Otherwise, return false
.
+ * @param collection the Collection to check
+ * @return whether the given Collection is empty
+ */
+ public static boolean isEmpty(Collection collection) {
+ return (collection == null || collection.isEmpty());
+ }
+
+ /**
+ * Returns the collection's size or {@code 0} if the collection is {@code null}.
+ *
+ * @param collection the collection to check.
+ * @return the collection's size or {@code 0} if the collection is {@code null}.
+ * @since 0.9.2
+ */
+ public static int size(Collection collection) {
+ return collection == null ? 0 : collection.size();
+ }
+
+ /**
+ * Returns the map's size or {@code 0} if the map is {@code null}.
+ *
+ * @param map the map to check
+ * @return the map's size or {@code 0} if the map is {@code null}.
+ * @since 0.9.2
+ */
+ public static int size(Map map) {
+ return map == null ? 0 : map.size();
+ }
+
+ /**
+ * Return true
if the supplied Map is null
+ * or empty. Otherwise, return false
.
+ * @param map the Map to check
+ * @return whether the given Map is empty
+ */
+ public static boolean isEmpty(Map map) {
+ return (map == null || map.isEmpty());
+ }
+
+ /**
+ * Convert the supplied array into a List. A primitive array gets
+ * converted into a List of the appropriate wrapper type.
+ * null
source value will be converted to an
+ * empty List.
+ * @param source the (potentially primitive) array
+ * @return the converted List result
+ * @see Objects#toObjectArray(Object)
+ */
+ public static List arrayToList(Object source) {
+ return Arrays.asList(Objects.toObjectArray(source));
+ }
+
+ /**
+ * Merge the given array into the given Collection.
+ * @param array the array to merge (may be null
)
+ * @param collection the target Collection to merge the array into
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergeArrayIntoCollection(Object array, Collection collection) {
+ if (collection == null) {
+ throw new IllegalArgumentException("Collection must not be null");
+ }
+ Object[] arr = Objects.toObjectArray(array);
+ for (Object elem : arr) {
+ collection.add(elem);
+ }
+ }
+
+ /**
+ * Merge the given Properties instance into the given Map,
+ * copying all properties (key-value pairs) over.
+ * Properties.propertyNames()
to even catch
+ * default properties linked into the original Properties instance.
+ * @param props the Properties instance to merge (may be null
)
+ * @param map the target Map to merge the properties into
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergePropertiesIntoMap(Properties props, Map map) {
+ if (map == null) {
+ throw new IllegalArgumentException("Map must not be null");
+ }
+ if (props != null) {
+ for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+ String key = (String) en.nextElement();
+ Object value = props.getProperty(key);
+ if (value == null) {
+ // Potentially a non-String value...
+ value = props.get(key);
+ }
+ map.put(key, value);
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the given Iterator contains the given element.
+ * @param iterator the Iterator to check
+ * @param element the element to look for
+ * @return true
if found, false
else
+ */
+ public static boolean contains(Iterator iterator, Object element) {
+ if (iterator != null) {
+ while (iterator.hasNext()) {
+ Object candidate = iterator.next();
+ if (Objects.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Enumeration contains the given element.
+ * @param enumeration the Enumeration to check
+ * @param element the element to look for
+ * @return true
if found, false
else
+ */
+ public static boolean contains(Enumeration enumeration, Object element) {
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ Object candidate = enumeration.nextElement();
+ if (Objects.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Collection contains the given element instance.
+ * true
for an equal element as well.
+ * @param collection the Collection to check
+ * @param element the element to look for
+ * @return true
if found, false
else
+ */
+ public static boolean containsInstance(Collection collection, Object element) {
+ if (collection != null) {
+ for (Object candidate : collection) {
+ if (candidate == element) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return true
if any element in 'candidates
' is
+ * contained in 'source
'; otherwise returns false
.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return whether any of the candidates has been found
+ */
+ public static boolean containsAny(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return false;
+ }
+ for (Object candidate : candidates) {
+ if (source.contains(candidate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the first element in 'candidates
' that is contained in
+ * 'source
'. If no element in 'candidates
' is present in
+ * 'source
' returns null
. Iteration order is
+ * {@link Collection} implementation specific.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return the first present object, or null
if not found
+ */
+ public static Object findFirstMatch(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return null;
+ }
+ for (Object candidate : candidates) {
+ if (source.contains(candidate)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find a single value of the given type in the given Collection.
+ * @param collection the Collection to search
+ * @param type the type to look for
+ * @return a value of the given type found if there is a clear match,
+ * or null
if none or more than one such value found
+ */
+ @SuppressWarnings("unchecked")
+ public static null
if none or more than one such value found
+ */
+ public static Object findValueOfType(Collection> collection, Class>[] types) {
+ if (isEmpty(collection) || Objects.isEmpty(types)) {
+ return null;
+ }
+ for (Class> type : types) {
+ Object value = findValueOfType(collection, type);
+ if (value != null) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given Collection only contains a single unique object.
+ * @param collection the Collection to check
+ * @return true
if the collection contains a single reference or
+ * multiple references to the same instance, false
else
+ */
+ public static boolean hasUniqueObject(Collection collection) {
+ if (isEmpty(collection)) {
+ return false;
+ }
+ boolean hasCandidate = false;
+ Object candidate = null;
+ for (Object elem : collection) {
+ if (!hasCandidate) {
+ hasCandidate = true;
+ candidate = elem;
+ }
+ else if (candidate != elem) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Find the common element type of the given Collection, if any.
+ * @param collection the Collection to check
+ * @return the common element type, or null
if no clear
+ * common type has been found (or the collection was empty)
+ */
+ public static Class> findCommonElementType(Collection collection) {
+ if (isEmpty(collection)) {
+ return null;
+ }
+ Class> candidate = null;
+ for (Object val : collection) {
+ if (val != null) {
+ if (candidate == null) {
+ candidate = val.getClass();
+ }
+ else if (candidate != val.getClass()) {
+ return null;
+ }
+ }
+ }
+ return candidate;
+ }
+
+ /**
+ * Marshal the elements from the given enumeration into an array of the given type.
+ * Enumeration elements must be assignable to the type of the given array. The array
+ * returned will be a different instance than the array given.
+ */
+ public static A[] toArray(Enumeration{@code
+ * Map
+ * @param key the key of an map entry to be added
+ * @param value the value of map entry to be added
+ * @param {@code
+ * Map
+ * @param null
or of zero length.
+ *
+ * @param array the array to check
+ */
+ public static boolean isEmpty(Object[] array) {
+ return (array == null || array.length == 0);
+ }
+
+ /**
+ * Returns {@code true} if the specified byte array is null or of zero length, {@code false} otherwise.
+ *
+ * @param array the byte array to check
+ * @return {@code true} if the specified byte array is null or of zero length, {@code false} otherwise.
+ */
+ public static boolean isEmpty(byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Check whether the given array contains the given element.
+ *
+ * @param array the array to check (may be null
,
+ * in which case the return value will always be false
)
+ * @param element the element to check for
+ * @return whether the element has been found in the given array
+ */
+ public static boolean containsElement(Object[] array, Object element) {
+ if (array == null) {
+ return false;
+ }
+ for (Object arrayEle : array) {
+ if (nullSafeEquals(arrayEle, element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given array of enum constants contains a constant with the given name,
+ * ignoring case when determining a match.
+ *
+ * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
+ * @param constant the constant name to find (must not be null or empty string)
+ * @return whether the constant has been found in the given array
+ */
+ public static boolean containsConstant(Enum>[] enumValues, String constant) {
+ return containsConstant(enumValues, constant, false);
+ }
+
+ /**
+ * Check whether the given array of enum constants contains a constant with the given name.
+ *
+ * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
+ * @param constant the constant name to find (must not be null or empty string)
+ * @param caseSensitive whether case is significant in determining a match
+ * @return whether the constant has been found in the given array
+ */
+ public static boolean containsConstant(Enum>[] enumValues, String constant, boolean caseSensitive) {
+ for (Enum> candidate : enumValues) {
+ if (caseSensitive ?
+ candidate.toString().equals(constant) :
+ candidate.toString().equalsIgnoreCase(constant)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
+ *
+ * @param null
)
+ * @param obj the object to append
+ * @return the new array (of the same component type; never null
)
+ */
+ public static A[] addObjectToArray(A[] array, O obj) {
+ Class> compType = Object.class;
+ if (array != null) {
+ compType = array.getClass().getComponentType();
+ } else if (obj != null) {
+ compType = obj.getClass();
+ }
+ int newArrLength = (array != null ? array.length + 1 : 1);
+ @SuppressWarnings("unchecked")
+ A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
+ if (array != null) {
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ }
+ newArr[newArr.length - 1] = obj;
+ return newArr;
+ }
+
+ /**
+ * Convert the given array (which may be a primitive array) to an
+ * object array (if necessary of primitive wrapper objects).
+ * null
source value will be converted to an
+ * empty Object array.
+ *
+ * @param source the (potentially primitive) array
+ * @return the corresponding object array (never null
)
+ * @throws IllegalArgumentException if the parameter is not an array
+ */
+ public static Object[] toObjectArray(Object source) {
+ if (source instanceof Object[]) {
+ return (Object[]) source;
+ }
+ if (source == null) {
+ return new Object[0];
+ }
+ if (!source.getClass().isArray()) {
+ throw new IllegalArgumentException("Source is not an array: " + source);
+ }
+ int length = Array.getLength(source);
+ if (length == 0) {
+ return new Object[0];
+ }
+ Class wrapperType = Array.get(source, 0).getClass();
+ Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
+ for (int i = 0; i < length; i++) {
+ newArray[i] = Array.get(source, i);
+ }
+ return newArray;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for content-based equality/hash-code handling
+ //---------------------------------------------------------------------
+
+ /**
+ * Determine if the given objects are equal, returning true
+ * if both are null
or false
if only one is
+ * null
.
+ * Arrays.equals
, performing an equality
+ * check based on the array elements rather than the array reference.
+ *
+ * @param o1 first Object to compare
+ * @param o2 second Object to compare
+ * @return whether the given objects are equal
+ * @see java.util.Arrays#equals
+ */
+ public static boolean nullSafeEquals(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+ if (o1.equals(o2)) {
+ return true;
+ }
+ if (o1.getClass().isArray() && o2.getClass().isArray()) {
+ if (o1 instanceof Object[] && o2 instanceof Object[]) {
+ return Arrays.equals((Object[]) o1, (Object[]) o2);
+ }
+ if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+ return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+ }
+ if (o1 instanceof byte[] && o2 instanceof byte[]) {
+ return Arrays.equals((byte[]) o1, (byte[]) o2);
+ }
+ if (o1 instanceof char[] && o2 instanceof char[]) {
+ return Arrays.equals((char[]) o1, (char[]) o2);
+ }
+ if (o1 instanceof double[] && o2 instanceof double[]) {
+ return Arrays.equals((double[]) o1, (double[]) o2);
+ }
+ if (o1 instanceof float[] && o2 instanceof float[]) {
+ return Arrays.equals((float[]) o1, (float[]) o2);
+ }
+ if (o1 instanceof int[] && o2 instanceof int[]) {
+ return Arrays.equals((int[]) o1, (int[]) o2);
+ }
+ if (o1 instanceof long[] && o2 instanceof long[]) {
+ return Arrays.equals((long[]) o1, (long[]) o2);
+ }
+ if (o1 instanceof short[] && o2 instanceof short[]) {
+ return Arrays.equals((short[]) o1, (short[]) o2);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return as hash code for the given object; typically the value of
+ * {@link Object#hashCode()}
. If the object is an array,
+ * this method will delegate to any of the nullSafeHashCode
+ * methods for arrays in this class. If the object is null
,
+ * this method returns 0.
+ *
+ * @see #nullSafeHashCode(Object[])
+ * @see #nullSafeHashCode(boolean[])
+ * @see #nullSafeHashCode(byte[])
+ * @see #nullSafeHashCode(char[])
+ * @see #nullSafeHashCode(double[])
+ * @see #nullSafeHashCode(float[])
+ * @see #nullSafeHashCode(int[])
+ * @see #nullSafeHashCode(long[])
+ * @see #nullSafeHashCode(short[])
+ */
+ public static int nullSafeHashCode(Object obj) {
+ if (obj == null) {
+ return 0;
+ }
+ if (obj.getClass().isArray()) {
+ if (obj instanceof Object[]) {
+ return nullSafeHashCode((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeHashCode((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeHashCode((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeHashCode((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeHashCode((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeHashCode((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeHashCode((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeHashCode((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeHashCode((short[]) obj);
+ }
+ }
+ return obj.hashCode();
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(Object[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + nullSafeHashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(boolean[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(byte[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(char[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(double[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(float[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(int[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(long[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array
is null
, this method returns 0.
+ */
+ public static int nullSafeHashCode(short[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return the same value as {@link Boolean#hashCode()}
.
+ *
+ * @see Boolean#hashCode()
+ */
+ public static int hashCode(boolean bool) {
+ return bool ? 1231 : 1237;
+ }
+
+ /**
+ * Return the same value as {@link Double#hashCode()}
.
+ *
+ * @see Double#hashCode()
+ */
+ public static int hashCode(double dbl) {
+ long bits = Double.doubleToLongBits(dbl);
+ return hashCode(bits);
+ }
+
+ /**
+ * Return the same value as {@link Float#hashCode()}
.
+ *
+ * @see Float#hashCode()
+ */
+ public static int hashCode(float flt) {
+ return Float.floatToIntBits(flt);
+ }
+
+ /**
+ * Return the same value as {@link Long#hashCode()}
.
+ *
+ * @see Long#hashCode()
+ */
+ public static int hashCode(long lng) {
+ return (int) (lng ^ (lng >>> 32));
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for toString output
+ //---------------------------------------------------------------------
+
+ /**
+ * Return a String representation of an object's overall identity.
+ *
+ * @param obj the object (may be null
)
+ * @return the object's identity as String representation,
+ * or an empty String if the object was null
+ */
+ public static String identityToString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return obj.getClass().getName() + "@" + getIdentityHexString(obj);
+ }
+
+ /**
+ * Return a hex String form of an object's identity hash code.
+ *
+ * @param obj the object
+ * @return the object's identity code in hex notation
+ */
+ public static String getIdentityHexString(Object obj) {
+ return Integer.toHexString(System.identityHashCode(obj));
+ }
+
+ /**
+ * Return a content-based String representation if obj
is
+ * not null
; otherwise returns an empty String.
+ * null
value.
+ *
+ * @param obj the object to build a display String for
+ * @return a display String representation of obj
+ * @see #nullSafeToString(Object)
+ */
+ public static String getDisplayString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return nullSafeToString(obj);
+ }
+
+ /**
+ * Determine the class name for the given object.
+ * "null"
if obj
is null
.
+ *
+ * @param obj the object to introspect (may be null
)
+ * @return the corresponding class name
+ */
+ public static String nullSafeClassName(Object obj) {
+ return (obj != null ? obj.getClass().getName() : NULL_STRING);
+ }
+
+ /**
+ * Return a String representation of the specified Object.
+ * "null"
if obj
is null
.
+ *
+ * @param obj the object to build a String representation for
+ * @return a String representation of obj
+ */
+ public static String nullSafeToString(Object obj) {
+ if (obj == null) {
+ return NULL_STRING;
+ }
+ if (obj instanceof String) {
+ return (String) obj;
+ }
+ if (obj instanceof Object[]) {
+ return nullSafeToString((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeToString((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeToString((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeToString((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeToString((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeToString((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeToString((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeToString((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeToString((short[]) obj);
+ }
+ String str = obj.toString();
+ return (str != null ? str : EMPTY_STRING);
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(Object[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(String.valueOf(array[i]));
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(boolean[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(byte[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(char[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append("'").append(array[i]).append("'");
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(double[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(float[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(int[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(long[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * "{}"
). Adjacent elements are separated
+ * by the characters ", "
(a comma followed by a space). Returns
+ * "null"
if array
is null
.
+ *
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(short[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ } else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ public static void nullSafeClose(Closeable... closeables) {
+ if (closeables == null) {
+ return;
+ }
+
+ for (Closeable closeable : closeables) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ //Ignore the exception during close.
+ }
+ }
+ }
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/RuntimeEnvironment.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/RuntimeEnvironment.java
new file mode 100644
index 000000000000..df5b8c7c7ae5
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/RuntimeEnvironment.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.lang;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public final class RuntimeEnvironment {
+
+ private RuntimeEnvironment(){} //prevent instantiation
+
+ private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
+
+ private static final AtomicBoolean bcLoaded = new AtomicBoolean(false);
+
+ public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME);
+
+ public static void enableBouncyCastleIfPossible() {
+
+ if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) {
+ return;
+ }
+
+ try {
+ Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME);
+
+ //check to see if the user has already registered the BC provider:
+
+ Provider[] providers = Security.getProviders();
+
+ for(Provider provider : providers) {
+ if (clazz.isInstance(provider)) {
+ bcLoaded.set(true);
+ return;
+ }
+ }
+
+ //bc provider not enabled - add it:
+ Security.addProvider((Provider)Classes.newInstance(clazz));
+ bcLoaded.set(true);
+
+ } catch (UnknownClassException e) {
+ //not available
+ }
+ }
+
+ static {
+ enableBouncyCastleIfPossible();
+ }
+
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Strings.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Strings.java
new file mode 100644
index 000000000000..065bb2512bc8
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/Strings.java
@@ -0,0 +1,1147 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.lang;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+public final class Strings {
+
+ private static final String FOLDER_SEPARATOR = "/";
+
+ private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
+
+ private static final String TOP_PATH = "..";
+
+ private static final String CURRENT_PATH = ".";
+
+ private static final char EXTENSION_SEPARATOR = '.';
+
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private Strings(){} //prevent instantiation
+
+ //---------------------------------------------------------------------
+ // General convenience methods for working with Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Check that the given CharSequence is neither null
nor of length 0.
+ * Note: Will return true
for a CharSequence that purely consists of whitespace.
+ *
+ * Strings.hasLength(null) = false
+ * Strings.hasLength("") = false
+ * Strings.hasLength(" ") = true
+ * Strings.hasLength("Hello") = true
+ *
+ * @param str the CharSequence to check (may be null
)
+ * @return true
if the CharSequence is not null and has length
+ * @see #hasText(String)
+ */
+ public static boolean hasLength(CharSequence str) {
+ return (str != null && str.length() > 0);
+ }
+
+ /**
+ * Check that the given String is neither null
nor of length 0.
+ * Note: Will return true
for a String that purely consists of whitespace.
+ * @param str the String to check (may be null
)
+ * @return true
if the String is not null and has length
+ * @see #hasLength(CharSequence)
+ */
+ public static boolean hasLength(String str) {
+ return hasLength((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence has actual text.
+ * More specifically, returns true
if the string not null
,
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ *
+ * Strings.hasText(null) = false
+ * Strings.hasText("") = false
+ * Strings.hasText(" ") = false
+ * Strings.hasText("12345") = true
+ * Strings.hasText(" 12345 ") = true
+ *
+ * @param str the CharSequence to check (may be null
)
+ * @return true
if the CharSequence is not null
,
+ * its length is greater than 0, and it does not contain whitespace only
+ * @see java.lang.Character#isWhitespace
+ */
+ public static boolean hasText(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String has actual text.
+ * More specifically, returns true
if the string not null
,
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ * @param str the String to check (may be null
)
+ * @return true
if the String is not null
, its length is
+ * greater than 0, and it does not contain whitespace only
+ * @see #hasText(CharSequence)
+ */
+ public static boolean hasText(String str) {
+ return hasText((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence contains any whitespace characters.
+ * @param str the CharSequence to check (may be null
)
+ * @return true
if the CharSequence is not empty and
+ * contains at least 1 whitespace character
+ * @see java.lang.Character#isWhitespace
+ */
+ public static boolean containsWhitespace(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String contains any whitespace characters.
+ * @param str the String to check (may be null
)
+ * @return true
if the String is not empty and
+ * contains at least 1 whitespace character
+ * @see #containsWhitespace(CharSequence)
+ */
+ public static boolean containsWhitespace(String str) {
+ return containsWhitespace((CharSequence) str);
+ }
+
+ /**
+ * Trim leading and trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimWhitespace(String str) {
+ return (String) trimWhitespace((CharSequence)str);
+ }
+
+
+ private static CharSequence trimWhitespace(CharSequence str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ final int length = str.length();
+
+ int start = 0;
+ while (start < length && Character.isWhitespace(str.charAt(start))) {
+ start++;
+ }
+
+ int end = length;
+ while (start < length && Character.isWhitespace(str.charAt(end - 1))) {
+ end--;
+ }
+
+ return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str;
+ }
+
+ public static String clean(String str) {
+ CharSequence result = clean((CharSequence) str);
+
+ return result!=null?result.toString():null;
+ }
+
+ public static CharSequence clean(CharSequence str) {
+ str = trimWhitespace(str);
+ if (!hasLength(str)) {
+ return null;
+ }
+ return str;
+ }
+
+ /**
+ * Trim all whitespace from the given String:
+ * leading, trailing, and inbetween characters.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimAllWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ int index = 0;
+ while (sb.length() > index) {
+ if (Character.isWhitespace(sb.charAt(index))) {
+ sb.deleteCharAt(index);
+ }
+ else {
+ index++;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim leading whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimLeadingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
+ sb.deleteCharAt(0);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimTrailingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim all occurences of the supplied leading character from the given String.
+ * @param str the String to check
+ * @param leadingCharacter the leading character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimLeadingCharacter(String str, char leadingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) {
+ sb.deleteCharAt(0);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim all occurences of the supplied trailing character from the given String.
+ * @param str the String to check
+ * @param trailingCharacter the trailing character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimTrailingCharacter(String str, char trailingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Test if the given String starts with the specified prefix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param prefix the prefix to look for
+ * @see java.lang.String#startsWith
+ */
+ public static boolean startsWithIgnoreCase(String str, String prefix) {
+ if (str == null || prefix == null) {
+ return false;
+ }
+ if (str.startsWith(prefix)) {
+ return true;
+ }
+ if (str.length() < prefix.length()) {
+ return false;
+ }
+ String lcStr = str.substring(0, prefix.length()).toLowerCase();
+ String lcPrefix = prefix.toLowerCase();
+ return lcStr.equals(lcPrefix);
+ }
+
+ /**
+ * Test if the given String ends with the specified suffix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param suffix the suffix to look for
+ * @see java.lang.String#endsWith
+ */
+ public static boolean endsWithIgnoreCase(String str, String suffix) {
+ if (str == null || suffix == null) {
+ return false;
+ }
+ if (str.endsWith(suffix)) {
+ return true;
+ }
+ if (str.length() < suffix.length()) {
+ return false;
+ }
+
+ String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();
+ String lcSuffix = suffix.toLowerCase();
+ return lcStr.equals(lcSuffix);
+ }
+
+ /**
+ * Test whether the given string matches the given substring
+ * at the given index.
+ * @param str the original string (or StringBuilder)
+ * @param index the index in the original string to start matching against
+ * @param substring the substring to match at the given index
+ */
+ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
+ for (int j = 0; j < substring.length(); j++) {
+ int i = index + j;
+ if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Count the occurrences of the substring in string s.
+ * @param str string to search in. Return 0 if this is null.
+ * @param sub string to search for. Return 0 if this is null.
+ */
+ public static int countOccurrencesOf(String str, String sub) {
+ if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {
+ return 0;
+ }
+ int count = 0;
+ int pos = 0;
+ int idx;
+ while ((idx = str.indexOf(sub, pos)) != -1) {
+ ++count;
+ pos = idx + sub.length();
+ }
+ return count;
+ }
+
+ /**
+ * Replace all occurences of a substring within a string with
+ * another string.
+ * @param inString String to examine
+ * @param oldPattern String to replace
+ * @param newPattern String to insert
+ * @return a String with the replacements
+ */
+ public static String replace(String inString, String oldPattern, String newPattern) {
+ if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
+ return inString;
+ }
+ StringBuilder sb = new StringBuilder();
+ int pos = 0; // our position in the old string
+ int index = inString.indexOf(oldPattern);
+ // the index of an occurrence we've found, or -1
+ int patLen = oldPattern.length();
+ while (index >= 0) {
+ sb.append(inString.substring(pos, index));
+ sb.append(newPattern);
+ pos = index + patLen;
+ index = inString.indexOf(oldPattern, pos);
+ }
+ sb.append(inString.substring(pos));
+ // remember to append any characters to the right of a match
+ return sb.toString();
+ }
+
+ /**
+ * Delete all occurrences of the given substring.
+ * @param inString the original String
+ * @param pattern the pattern to delete all occurrences of
+ * @return the resulting String
+ */
+ public static String delete(String inString, String pattern) {
+ return replace(inString, pattern, "");
+ }
+
+ /**
+ * Delete any character in a given String.
+ * @param inString the original String
+ * @param charsToDelete a set of characters to delete.
+ * E.g. "az\n" will delete 'a's, 'z's and new lines.
+ * @return the resulting String
+ */
+ public static String deleteAny(String inString, String charsToDelete) {
+ if (!hasLength(inString) || !hasLength(charsToDelete)) {
+ return inString;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < inString.length(); i++) {
+ char c = inString.charAt(i);
+ if (charsToDelete.indexOf(c) == -1) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with formatted Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Quote the given String with single quotes.
+ * @param str the input String (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or null
if the input was null
+ */
+ public static String quote(String str) {
+ return (str != null ? "'" + str + "'" : null);
+ }
+
+ /**
+ * Turn the given Object into a String with single quotes
+ * if it is a String; keeping the Object as-is else.
+ * @param obj the input Object (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or the input object as-is if not a String
+ */
+ public static Object quoteIfString(Object obj) {
+ return (obj instanceof String ? quote((String) obj) : obj);
+ }
+
+ /**
+ * Unqualify a string qualified by a '.' dot character. For example,
+ * "this.name.is.qualified", returns "qualified".
+ * @param qualifiedName the qualified name
+ */
+ public static String unqualify(String qualifiedName) {
+ return unqualify(qualifiedName, '.');
+ }
+
+ /**
+ * Unqualify a string qualified by a separator character. For example,
+ * "this:name:is:qualified" returns "qualified" if using a ':' separator.
+ * @param qualifiedName the qualified name
+ * @param separator the separator
+ */
+ public static String unqualify(String qualifiedName, char separator) {
+ return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
+ }
+
+ /**
+ * Capitalize a String
, changing the first letter to
+ * upper case as per {@link Character#toUpperCase(char)}.
+ * No other letters are changed.
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, null
if null
+ */
+ public static String capitalize(String str) {
+ return changeFirstCharacterCase(str, true);
+ }
+
+ /**
+ * Uncapitalize a String
, changing the first letter to
+ * lower case as per {@link Character#toLowerCase(char)}.
+ * No other letters are changed.
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, null
if null
+ */
+ public static String uncapitalize(String str) {
+ return changeFirstCharacterCase(str, false);
+ }
+
+ private static String changeFirstCharacterCase(String str, boolean capitalize) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str.length());
+ if (capitalize) {
+ sb.append(Character.toUpperCase(str.charAt(0)));
+ }
+ else {
+ sb.append(Character.toLowerCase(str.charAt(0)));
+ }
+ sb.append(str.substring(1));
+ return sb.toString();
+ }
+
+ /**
+ * Extract the filename from the given path,
+ * e.g. "mypath/myfile.txt" -> "myfile.txt".
+ * @param path the file path (may be null
)
+ * @return the extracted filename, or null
if none
+ */
+ public static String getFilename(String path) {
+ if (path == null) {
+ return null;
+ }
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
+ }
+
+ /**
+ * Extract the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "txt".
+ * @param path the file path (may be null
)
+ * @return the extracted filename extension, or null
if none
+ */
+ public static String getFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ if (extIndex == -1) {
+ return null;
+ }
+ int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (folderIndex > extIndex) {
+ return null;
+ }
+ return path.substring(extIndex + 1);
+ }
+
+ /**
+ * Strip the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "mypath/myfile".
+ * @param path the file path (may be null
)
+ * @return the path with stripped filename extension,
+ * or null
if none
+ */
+ public static String stripFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ if (extIndex == -1) {
+ return path;
+ }
+ int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (folderIndex > extIndex) {
+ return path;
+ }
+ return path.substring(0, extIndex);
+ }
+
+ /**
+ * Apply the given relative path to the given path,
+ * assuming standard Java folder separation (i.e. "/" separators).
+ * @param path the path to start from (usually a full file path)
+ * @param relativePath the relative path to apply
+ * (relative to the full file path above)
+ * @return the full file path that results from applying the relative path
+ */
+ public static String applyRelativePath(String path, String relativePath) {
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (separatorIndex != -1) {
+ String newPath = path.substring(0, separatorIndex);
+ if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
+ newPath += FOLDER_SEPARATOR;
+ }
+ return newPath + relativePath;
+ }
+ else {
+ return relativePath;
+ }
+ }
+
+ /**
+ * Normalize the path by suppressing sequences like "path/.." and
+ * inner simple dots.
+ * localeString
value into a {@link java.util.Locale}.
+ * Locale's
+ * toString()
format ("en", "en_UK", etc);
+ * also accepts spaces as separators, as an alternative to underscores
+ * @return a corresponding Locale
instance
+ */
+ public static Locale parseLocaleString(String localeString) {
+ String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
+ String language = (parts.length > 0 ? parts[0] : "");
+ String country = (parts.length > 1 ? parts[1] : "");
+ validateLocalePart(language);
+ validateLocalePart(country);
+ String variant = "";
+ if (parts.length >= 2) {
+ // There is definitely a variant, and it is everything after the country
+ // code sans the separator between the country code and the variant.
+ int endIndexOfCountryCode = localeString.indexOf(country) + country.length();
+ // Strip off any leading '_' and whitespace, what's left is the variant.
+ variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
+ if (variant.startsWith("_")) {
+ variant = trimLeadingCharacter(variant, '_');
+ }
+ }
+ return (language.length() > 0 ? new Locale(language, country, variant) : null);
+ }
+
+ 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");
+ }
+ }
+ }
+
+ /**
+ * Determine the RFC 3066 compliant language tag,
+ * as used for the HTTP "Accept-Language" header.
+ * @param locale the Locale to transform to a language tag
+ * @return the RFC 3066 compliant language tag as String
+ */
+ public static String toLanguageTag(Locale locale) {
+ return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with String arrays
+ //---------------------------------------------------------------------
+
+ /**
+ * Append the given String to the given String array, returning a new array
+ * consisting of the input array contents plus the given String.
+ * @param array the array to append to (can be null
)
+ * @param str the String to append
+ * @return the new array (never null
)
+ */
+ public static String[] addStringToArray(String[] array, String str) {
+ if (Objects.isEmpty(array)) {
+ return new String[] {str};
+ }
+ String[] newArr = new String[array.length + 1];
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ newArr[array.length] = str;
+ return newArr;
+ }
+
+ /**
+ * Concatenate the given String arrays into one,
+ * with overlapping array elements included twice.
+ * null
)
+ * @param array2 the second array (can be null
)
+ * @return the new array (null
if both given arrays were null
)
+ */
+ public static String[] concatenateStringArrays(String[] array1, String[] array2) {
+ if (Objects.isEmpty(array1)) {
+ return array2;
+ }
+ if (Objects.isEmpty(array2)) {
+ return array1;
+ }
+ String[] newArr = new String[array1.length + array2.length];
+ System.arraycopy(array1, 0, newArr, 0, array1.length);
+ System.arraycopy(array2, 0, newArr, array1.length, array2.length);
+ return newArr;
+ }
+
+ /**
+ * Merge the given String arrays into one, with overlapping
+ * array elements only included once.
+ * null
)
+ * @param array2 the second array (can be null
)
+ * @return the new array (null
if both given arrays were null
)
+ */
+ public static String[] mergeStringArrays(String[] array1, String[] array2) {
+ if (Objects.isEmpty(array1)) {
+ return array2;
+ }
+ if (Objects.isEmpty(array2)) {
+ return array1;
+ }
+ Listnull
)
+ */
+ public static String[] sortStringArray(String[] array) {
+ if (Objects.isEmpty(array)) {
+ return new String[0];
+ }
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Copy the given Collection into a String array.
+ * The Collection must contain String elements only.
+ * @param collection the Collection to copy
+ * @return the String array (null
if the passed-in
+ * Collection was null
)
+ */
+ public static String[] toStringArray(Collectionnull
if the passed-in
+ * Enumeration was null
)
+ */
+ public static String[] toStringArray(EnumerationString.trim()
on each of them.
+ * @param array the original String array
+ * @return the resulting array (of the same size) with trimmed elements
+ */
+ public static String[] trimArrayElements(String[] array) {
+ if (Objects.isEmpty(array)) {
+ return new String[0];
+ }
+ String[] result = new String[array.length];
+ for (int i = 0; i < array.length; i++) {
+ String element = array[i];
+ result[i] = (element != null ? element.trim() : null);
+ }
+ return result;
+ }
+
+ /**
+ * Remove duplicate Strings from the given array.
+ * Also sorts the array, as it uses a TreeSet.
+ * @param array the String array
+ * @return an array without duplicates, in natural sort order
+ */
+ public static String[] removeDuplicateStrings(String[] array) {
+ if (Objects.isEmpty(array)) {
+ return array;
+ }
+ Setnull
if the delimiter wasn't found in the given input String
+ */
+ public static String[] split(String toSplit, String delimiter) {
+ if (!hasLength(toSplit) || !hasLength(delimiter)) {
+ return null;
+ }
+ int offset = toSplit.indexOf(delimiter);
+ if (offset < 0) {
+ return null;
+ }
+ String beforeDelimiter = toSplit.substring(0, offset);
+ String afterDelimiter = toSplit.substring(offset + delimiter.length());
+ return new String[] {beforeDelimiter, afterDelimiter};
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A Properties
instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ * Properties
instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @return a Properties
instance representing the array contents,
+ * or null
if the array to process was null or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
+ return splitArrayElementsIntoProperties(array, delimiter, null);
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A Properties
instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ * Properties
instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @param charsToDelete one or more characters to remove from each element
+ * prior to attempting the split operation (typically the quotation mark
+ * symbol), or null
if no removal should occur
+ * @return a Properties
instance representing the array contents,
+ * or null
if the array to process was null
or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(
+ String[] array, String delimiter, String charsToDelete) {
+
+ if (Objects.isEmpty(array)) {
+ return null;
+ }
+ Properties result = new Properties();
+ for (String element : array) {
+ if (charsToDelete != null) {
+ element = deleteAny(element, charsToDelete);
+ }
+ String[] splittedElement = split(element, delimiter);
+ if (splittedElement == null) {
+ continue;
+ }
+ result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
+ }
+ return result;
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ * Trims tokens and omits empty tokens.
+ * delimitedListToStringArray
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter).
+ * @return an array of the tokens
+ * @see java.util.StringTokenizer
+ * @see java.lang.String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(String str, String delimiters) {
+ return tokenizeToStringArray(str, delimiters, true, true);
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ * delimitedListToStringArray
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter)
+ * @param trimTokens trim the tokens via String's trim
+ * @param ignoreEmptyTokens omit empty tokens from the result array
+ * (only applies to tokens that are empty after trimming; StringTokenizer
+ * will not consider subsequent delimiters as token in the first place).
+ * @return an array of the tokens (null
if the input String
+ * was null
)
+ * @see java.util.StringTokenizer
+ * @see java.lang.String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(
+ String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
+
+ if (str == null) {
+ return null;
+ }
+ StringTokenizer st = new StringTokenizer(str, delimiters);
+ ListtokenizeToStringArray
.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter) {
+ return delimitedListToStringArray(str, delimiter, null);
+ }
+
+ /**
+ * Take a String which is a delimited list and convert it to a String array.
+ * tokenizeToStringArray
.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @param charsToDelete a set of characters to delete. Useful for deleting unwanted
+ * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {
+ if (str == null) {
+ return new String[0];
+ }
+ if (delimiter == null) {
+ return new String[] {str};
+ }
+ ListtoString()
implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @param prefix the String to start each element with
+ * @param suffix the String to end each element with
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection> coll, String delim, String prefix, String suffix) {
+ if (Collections.isEmpty(coll)) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ Iterator> it = coll.iterator();
+ while (it.hasNext()) {
+ sb.append(prefix).append(it.next()).append(suffix);
+ if (it.hasNext()) {
+ sb.append(delim);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a Collection as a delimited (e.g. CSV)
+ * String. E.g. useful for toString()
implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection> coll, String delim) {
+ return collectionToDelimitedString(coll, delim, "", "");
+ }
+
+ /**
+ * Convenience method to return a Collection as a CSV String.
+ * E.g. useful for toString()
implementations.
+ * @param coll the Collection to display
+ * @return the delimited String
+ */
+ public static String collectionToCommaDelimitedString(Collection> coll) {
+ return collectionToDelimitedString(coll, ",");
+ }
+
+ /**
+ * Convenience method to return a String array as a delimited (e.g. CSV)
+ * String. E.g. useful for toString()
implementations.
+ * @param arr the array to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String arrayToDelimitedString(Object[] arr, String delim) {
+ if (Objects.isEmpty(arr)) {
+ return "";
+ }
+ if (arr.length == 1) {
+ return Objects.nullSafeToString(arr[0]);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ sb.append(delim);
+ }
+ sb.append(arr[i]);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a String array as a CSV String.
+ * E.g. useful for toString()
implementations.
+ * @param arr the array to display
+ * @return the delimited String
+ */
+ public static String arrayToCommaDelimitedString(Object[] arr) {
+ return arrayToDelimitedString(arr, ",");
+ }
+
+}
+
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/UnknownClassException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/UnknownClassException.java
new file mode 100644
index 000000000000..07b44d9fcab5
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/lang/UnknownClassException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.lang;
+
+/**
+ * A RuntimeException
equivalent of the JDK's
+ * ClassNotFoundException
, to maintain a RuntimeException paradigm.
+ *
+ * @since 0.1
+ */
+public class UnknownClassException extends RuntimeException {
+
+ /*
+ /**
+ * Creates a new UnknownClassException.
+ *
+ public UnknownClassException() {
+ super();
+ }*/
+
+ /**
+ * Constructs a new UnknownClassException.
+ *
+ * @param message the reason for the exception
+ */
+ public UnknownClassException(String message) {
+ super(message);
+ }
+
+ /*
+ * Constructs a new UnknownClassException.
+ *
+ * @param cause the underlying Throwable that caused this exception to be thrown.
+ *
+ public UnknownClassException(Throwable cause) {
+ super(cause);
+ }
+ */
+
+ /**
+ * Constructs a new UnknownClassException.
+ *
+ * @param message the reason for the exception
+ * @param cause the underlying Throwable that caused this exception to be thrown.
+ */
+ public UnknownClassException(String message, Throwable cause) {
+ // TODO: remove in v1.0, this constructor is only exposed to allow for backward compatible behavior
+ super(message, cause);
+ }
+
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/InvalidKeyException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/InvalidKeyException.java
new file mode 100644
index 000000000000..2e3b84b8af15
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/InvalidKeyException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class InvalidKeyException extends KeyException {
+
+ public InvalidKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/KeyException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/KeyException.java
new file mode 100644
index 000000000000..0db219245735
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/KeyException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class KeyException extends SecurityException {
+
+ public KeyException(String message) {
+ super(message);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/Keys.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/Keys.java
new file mode 100644
index 000000000000..cb4784248932
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/Keys.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Classes;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s.
+ *
+ * @since 0.10.0
+ */
+public final class Keys {
+
+ private static final String MAC = "io.jsonwebtoken.impl.crypto.MacProvider";
+ private static final String RSA = "io.jsonwebtoken.impl.crypto.RsaProvider";
+ private static final String EC = "io.jsonwebtoken.impl.crypto.EllipticCurveProvider";
+
+ private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class};
+
+ //purposefully ordered higher to lower:
+ private static final List
+ *
+ *
+ * @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use.
+ * @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}.
+ * @throws IllegalArgumentException for any input value other than {@link SignatureAlgorithm#HS256},
+ * {@link SignatureAlgorithm#HS384}, or {@link SignatureAlgorithm#HS512}
+ */
+ public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgumentException {
+ Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
+ switch (alg) {
+ case HS256:
+ case HS384:
+ case HS512:
+ return Classes.invokeStatic(MAC, "generateKey", SIG_ARG_TYPES, alg);
+ default:
+ String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ /**
+ * Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
+ *
+ *
+ *
+ * Algorithm
+ * Key Length
+ *
+ *
+ * HS256
+ * 256 bits (32 bytes)
+ *
+ *
+ * HS384
+ * 384 bits (48 bytes)
+ *
+ *
+ * HS512
+ * 512 bits (64 bytes)
+ *
+ *
+ *
+ *
+ *
+ * JWA Algorithm
+ * Key Size
+ *
+ *
+ * RS256
+ * 2048 bits
+ *
+ *
+ * PS256
+ * 2048 bits
+ *
+ *
+ * RS384
+ * 3072 bits
+ *
+ *
+ * PS384
+ * 3072 bits
+ *
+ *
+ * RS512
+ * 4096 bits
+ *
+ *
+ * PS512
+ * 4096 bits
+ *
+ *
+ *
+ * @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use.
+ * @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.
+ * @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm
+ */
+ public static KeyPair keyPairFor(SignatureAlgorithm alg) throws IllegalArgumentException {
+ Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
+ switch (alg) {
+ case RS256:
+ case PS256:
+ case RS384:
+ case PS384:
+ case RS512:
+ case PS512:
+ return Classes.invokeStatic(RSA, "generateKeyPair", SIG_ARG_TYPES, alg);
+ case ES256:
+ case ES384:
+ case ES512:
+ return Classes.invokeStatic(EC, "generateKeyPair", SIG_ARG_TYPES, alg);
+ default:
+ String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
+ throw new IllegalArgumentException(msg);
+ }
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SecurityException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SecurityException.java
new file mode 100644
index 000000000000..8b5f8abd87e3
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SecurityException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+import io.jsonwebtoken.JwtException;
+
+/**
+ * @since 0.10.0
+ */
+public class SecurityException extends JwtException {
+
+ public SecurityException(String message) {
+ super(message);
+ }
+
+ public SecurityException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SignatureException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SignatureException.java
new file mode 100644
index 000000000000..7cddb2cac36b
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/SignatureException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class SignatureException extends io.jsonwebtoken.SignatureException {
+
+ public SignatureException(String message) {
+ super(message);
+ }
+
+ public SignatureException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/WeakKeyException.java b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/WeakKeyException.java
new file mode 100644
index 000000000000..8a7688fed8bf
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jwtk-jjwt-0.11.2/io/jsonwebtoken/security/WeakKeyException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class WeakKeyException extends InvalidKeyException {
+
+ public WeakKeyException(String message) {
+ super(message);
+ }
+}
From 885044e3317d3644ddeab5f5f6c4d9dc920f8c36 Mon Sep 17 00:00:00 2001
From: intrigus
+ *
+ * JWA Algorithm
+ * Key Size
+ * JWA Curve Name
+ * ASN1 OID Curve Name
+ *
+ *
+ * EC256
+ * 256 bits
+ * {@code P-256}
+ * {@code secp256r1}
+ *
+ *
+ * EC384
+ * 384 bits
+ * {@code P-384}
+ * {@code secp384r1}
+ *
+ *
+ * EC512
+ * 512 bits
+ * {@code P-521}
+ * {@code secp521r1}
+ * X-Forwarded-For
, which is used to ensure
+security or track it in the log for statistical or other reasons. Attackers can use X-Forwarded-For
Spoofing software.bad1
to bad2
.
+In the bad1
method, the value of X-Forwarded-For
in header
is split, and the first value of
+the split array is obtained. Good case, such as good1
, split the value of X-Forwarded-For
in header
+and get the last value of the split array.
If an LDAP query is built by a not sanitized user-provided value, a user is likely to be able to run malicious LDAP queries.
-If an LDAP query or DN is built using string concatenation or string formatting, and the +components of the concatenation include user input without any proper sanitization, a user +is likely to be able to run malicious LDAP queries.
+If user input must be included in an LDAP query or DN, it should be escaped to
+avoid a malicious user providing special characters that change the meaning
+of the query. In Python2, user input should be escaped with ldap.dn.escape_dn_chars
+or ldap.filter.escape_filter_chars
, while in Python3, user input should be escaped with
+ldap3.utils.dn.escape_rdn
or ldap3.utils.conv.escape_filter_chars
+depending on the component tainted by the user. A good practice is to escape filter characters
+that could change the meaning of the query (https://tools.ietf.org/search/rfc4515#section-3).
In the following examples, the code accepts both username
and dc
from the user,
+which it then uses to build a LDAP query and DN.
The first and the second example uses the unsanitized user input directly +in the search filter and DN for the LDAP query. +A malicious user could provide special characters to change the meaning of these +components, and search for a completely different set of values.
-In case user input must compose an LDAP query, it should be escaped in order to avoid a malicious user supplying special characters that change the actual purpose of the query. To do so, functions that ldap frameworks provide such as In the third and four example, the input provided by the user is sanitized before it is included in the search filter or DN.
+This ensures the meaning of the query cannot be changed by a malicious user.escape_filter_chars
should be applied to that user input.
-
In the third and four example, the input provided by the user is sanitized before it is included in the search filter or DN. This ensures the meaning of the query cannot be changed by a malicious user.
-The software uses external input as the function name to wrap JSON data and returns it to the client as a request response. -When there is a cross-domain problem, the problem of sensitive information leakage may occur.
+When there is a cross-domain problem, this could lead to information leakage.The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad8
,
+
The following examples show the bad case and the good case respectively. Bad cases, such as bad1
to bad8
,
will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
method and the good2
method, use the verifToken
method to do the random token
Verification can
solve the problem of information leakage caused by cross-domain.
The following examples show the bad case and the good case respectively. Bad cases, such as bad1
to bad8
,
-will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
+will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1
method and the good2
method, use the verifToken
method to do the random token
Verification can
solve the problem of information leakage caused by cross-domain.
bad1
to bad8
,
will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1
-method and the good2
method, use the verifToken
method to do the random token
Verification can
+method and the good2
method, using the verifToken
method to do random token
verification
solve the problem of information leakage caused by cross-domain.
bad1
to bad8
,
will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1
method and the good2
method, using the verifToken
method to do random token
verification
-solve the problem of information leakage caused by cross-domain.
+solves the problem of information leakage even in the presence of cross-domain access issues.
A JWT consists of three parts: header, payload, and signature.
+The io.jsonwebtoken.jjwt
library is one of many libraries used for working with JWTs.
+It offers different methods for parsing tokens like parse
, parseClaimsJws
, and parsePlaintextJws
.
+The last two correctly verify that the JWT is properly signed.
+This is done by computing the signature of the combination of header and payload and
+comparing the locally computed signature with the signature part of the JWT.
+
+Therefore it is necessary to provide the JwtParser
with a key that is used for signature validation.
+Unfortunately the parse
method accepts a JWT whose signature is empty although a signing key has been set for the parser.
+This means that an attacker can create arbitrary JWTs that will be accepted.
+
Always verify the signature by using either the parseClaimsJws
and parsePlaintextJws
methods or
+by overriding the onPlaintextJws
or onClaimsJws
of JwtHandlerAdapter
.
+
The following example shows four cases where a signing key is set for a parser.
+In the first bad case the parse
method is used which will not validate the signature.
+The second bad case uses a JwtHandlerAdapter
where the onPlaintextJwt
method is overriden so it will not validate the signature.
+The third and fourth good cases use parseClaimsJws
method or override the onPlaintextJws
method.
+
In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources. These code snippets look suspicious and require the developer's attention.
+In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` forgetting to remove the old construct completely, you get `while(){...}while();` which may be vulnerable. These code snippets look suspicious and require the developer's attention.
-JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code, +JUEL does not support running expressions in a sandbox. To prevent running arbitrary code, incoming data has to be checked before including it in an expression. The next example uses a Regex pattern to check whether a user tries to run an allowed expression or not:
-The following examples show the bad case and the good case respectively. Bad cases, such as bad1
to bad8
,
+
The following examples show the bad case and the good case respectively. Bad cases, such as bad1
to bad7
,
will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1
-method and the good2
method, using the verifToken
method to do random token
verification
-solves the problem of information leakage even in the presence of cross-domain access issues.
good2
method, When these two methods process the request, there must be a request body in the request, which does not meet the conditions of Jsonp injection.
MBYi|TKi2k}LED>Hfac5SzB+I+H=P{uxu5nng1+YF0%q>@&d#4D zvUFaCc4~SZ@uAt|*;F*Qfa_PL$9`d^d>Sk*2KL7w-ps#-i+wB^K0N>kUV!y$T;1P- zAg^x^=;;2x>FNqfN=Z8U@zII8KN;(%i7!bhX;~H9z!3;i4D=!lDrE;dDoRSiP#QEJ zQY4Sa4U}kbm_9wpnbb0jABgOqyWGEaVEnKT2U}yyZ?5_G2ayl}qyNa;zsUs*I9VI% z+Z+Al`#GHWW3d3ir~oU}{@*-639t>3KkV`cl>|od!n|XI6LrZFG<@HUIk?gujanRq zBmE`VXh!gCbvs}hO|uw-sN;q=esYupSvd18X@hJH?Xh4 W*4uu3SBH?Tkaq21bxLQs<4>diP|ELB#30 zr8IW>D8vQLT?>Tr_LSMHpT;X?O-w&n0Wc~AEWQ5?R=+vGzd$SEIRc>R33l%;ViD%y zgK`d{qi0VZHeN%w>>G#V-6^j$S;4|wCeK!!+-u=n-+!$?c}~_~yfQc04lNfD?^zty z_gX{qYn1ntc`Db*tRywq$pJ 09hUYyZyDZ_D7jQ-$aW8UKx!&VgX>NiX-4Fvob EWTSAtVX=9iD~Li zQorv@@WO4Dd!vt!KUt`#|Ag@s02O3VAfOk&j`R}{BG!(8;lyvpRl8!%Q`3F*l!nJI zAoqkYNHsxSJ~*@G{zMuDN+Oj*8S4C0#;sUnpVGL5V#MoocbAM{8D0_uGe6{o8I2ln z `X {LRXq@Dw+GstX;y zXP$%I@cxW&;FIo!XJ8sW;7u-ma*QS6LbTjyp{1>jr?+s%V#XhJ9Fj*A*u}QX?lZFt z7vp0y!X lvGsJf21qoV>DwbXFxD_g^5NxMU+pO3XpH>s}O{M-OZ2tAjS~4 zkne;+1vhC2kW3LNm;2@hSbNcj&k>={uWn$2E(HDXGJKipe=_N6WT>7FK;Z@jVDbOM z%_sq8l$^e6AX0hAewh)j{hsCvF#cD524ivYo@8PydL6gT;*!I{C7!4m0 {TejvnUZe@0tIWKl^t`p3O;O*n>$B`Ph#RpfeHM)*W+2EVHrOn-$hvPAio7)RU z2CR#sk5?BwUpdpp?kI-9SdiE1v%De&Fw8ED@WEsAJxw=lpx5e3K#hRy&7o!Xfiyp1 zKoa|W> h`9@>V^9Pkq_lTq_BeYZ-dRjsWtSxg5vhAf=$t6PCsvIUAJqlsJ*!l ze2*X1n)dPSJ b$R9)Xyi`JThd>LKVjp&8qWhfD0J z<^7hL|?t{iMUazyj(pm=eUyLrn4N3;~UTf)*cksJLD?>#oYqg5|b;(YAYOra7Iu zV^4@LLm}uC`0|E(rp(2vK*AUNT^jYhn3K0a( a anA!@mWm-^Q_ji1){;Qx-jpXr8BPD+4fOeA%~zaCkxxm4LErp#f)xwt0zs ztCPVhfi-Oy&j6biQNm1aobzVQHgsz!%oM6%JXxV7dP(q-wIji;&z^V=5yW^eSDX-e z1uJ ?b2j1?r*F6uW-t z*$OD@E*eHv*?{Ws!WN9;5wY@cXZ03?et5IpVkexW%l3v?DAgR9AbM_6TvaMsHRLg7 zJBXv%*yeMqIYY4J4iiUhpsm8GVN<@1t2viSPnkOby9n2!GZjqhbmhs&aqn`UVnMV1 zVrK2JWU^AMi|R)-a1>{9U21AcbFYGR9u|+D?ssQqH|Et9Z`I^tAzVw5)j~>9gWp^h z9NJuWG+L!C;U69OA^uf~agGR5Edb@^0~Gmd`TcK7{GPV_pv8jd3BZ&jTt8rj2M;~p z-6xd2J1yJY*Rp@9qM< cd(c*mCxq+peBR^RJ-xN#aK8{U%xGfx_q zQF_dtKE*N^>PVulFuywT$(8D@%eTqE8OfvM9;AaJk}jOu0X}FN5E8EZ 0Ex=RfVu{7 z4dvUR7#ZDd@s1Hmc;b^9E$5Q_6)1gyy!` KXUW0N1?$z`w*^ dMhqfw??X%*5ID`Q`V@I!Vpp*U z*`7^&E}jIpGLNveGkptUoPTr1(Vw`;NdnqKC1YZDm$7ThRn#f9;LQJ#RqJDPPZ6-5 zGtV)OGH^t_mE)3597{F)%xgwL1BA^k=d&|-rydC`AtR6;{ivY}Vj1%Edn?DNse8;J zmd_pyVfhHfn&ql@;5Q9z{ugELQ*NM$GeB{pw>YeZDjm(<-A2ZbsFcktpH$U0o|jko zf2p70^!!G${yi)3YZZf~zV-JThJP|?*!~!h)_@%LJp>RC&o8O#n=n}?OUpk7q#~8S z4G6U#s=?Vyf@(R}?d!Am^;jHbOQKR#r+=Rhl8Vvx>YalC%m?L{_l|=Icrpd&vD*-= zkM7g@GB+u=uzIxH*}fpiLYx>l{R-A7Adj{a3$3?i6`ynYm?eW6qPup6QqTv}RzBJL zhG}dV{u4XsZCJlO*(MMEJ9BZ4CEbj+Rw$&3i^*z?eESVL6o^K72Vb8~$ytP^07X_! zTXLxYI+rwc(>Ex|T{pZznw+8s5icekOA%3hn|TjzmpO`#b@Qg2TV*uz)2ZtSLg|;H ztS?5E+LGZzKRwn#QcY?y-oDuj($uN%Vws{|3LAuJi2Pg*Kb@iE`)S^GVAl;ub c7=c}+FH6tLX}@9y{o8vFkAq{^o&l|tHWKrr_r)z@WY6QYS$_V z{pp7pkCgSX14r LTYby(BR8A1O5=$*NvUR1DRd zz6z*!6DBDknKZ+(e%Go=tPYOVjeC+Dp7Zq@9VRk1!0{2=NBm&cLP)$`AKp6QE+vQ> zQ$lbw-tNqww-!q>CBF2XOi}3-yd=tpt+BS0uIL6!){cxO{$Qpr_hWC}MJ84|GTMwx zBBQvlGenk+y4&G-jgWDJl4dtzVWb552`iEhtExecxT@u+FRPF;$qP!6@-Yn-N^r9) zERZCv#nBIY9Ip)T6ux@;U#aHT2Ob(c|6=$B7{}4_R&mvtykfAkQpkjczUh^}Lsdzg z?YaVDv@Df=>%k###exC^6Mymw2eNKFsb5@z$EQr zPY(8E6Q>O+57VX{lGGd?m6p6WaiCVdr*ceDiPk#_V%%45}p>jubIL5Eom@ zz*Cbnyk(R(@b1Dm?^1F24}{&ju49KxbM z6SzOhT*GrTgLu%yJ%!;)^uRTX?1djG#Crhe{n?Sx>EJlx6RK6_yG752LCP0ec)?W{ zOJj~V^eH@YO9y4Ir7#gMjlH<@(v#q$%{p_Xl5M$CQWa<(W_pxrr5-{5jNw1u6#lh2 zN_%4~8%MyE&2Mr1dzATE;I9>Ae+Ya!H_fe`ZC)AM|8Ph=-CF2Xo2$$LL>olFA@OTm z*xw3&s{SOtKS&91{m$Ib7{EXR`ftB?#NNmt`0+oH47pOPqu|;&s2VBz(ET}j;3ni5 zVPHM0bbNLaE>%^huXNs4HlPb`4CfE-l*L+wuXok!Tl(+!w+R#9(a#PR(S~Q43L$74 zD*2UZ-dXTQbKAQf80rZ?9D#ere41K`ZJq!z4XYFErU|hW^8f6;m~5O~_izG&9xdOH z6uf*HDN9&!0k!?sCDWZOQPp9uz@H5}h>Ydo2R8X< (lo@(rWKZX5^P{k)>F@D1O`?WLx+`nJ>{R`yiBj0YSiEi>1ok80Jn?R^?_`XUR zv_KK_Uh(5!X*c%#yr!qoOvx#k2|7Q5x^lIv&iwXuI$7QlSwRimT!3o|a3Wy|55?+N zaT`yUO~zBiK)d>hyO- vRdeeVL=>H)Qa9IFw>FEcj`dRL; zHNk(D`?vGzPkr{R{!#`{fP$Y=-B-V)=zn;GfPsUfJ>Zh<@3&%%jg)@7TPx^fZuxsR z-Z( DG|Q!Wh*DpjcQ+@Izp2S3h$mc%kilX>w)cQ+n~%j~ADZ?yy1Fr~*?vhq<)o ziXPLNj-L_)l^PlmMaX3dM8b0HV(J{EaG4>yYElgtp|D>C9 RpCCY7mJWpG!i` #}j?d4Wlcqu75 zM}Rf$Bw;>U0RwDJ5QN5$zPjoB!L9O!>UzlewYI?f;uZCGn{T{24t{F6r_rEzJP~Jl zfc5nA-*@bP(D&c%kZ$;oW-cQzteHoX4 ~NSvLAOZ=IW;zDD4MauWTd zIbXo~wU*Ssv7bNe_j{L+7qBIO(tx_W3(tTkVSPSRf}ZJtY;(oT8Hh!Z@Q17-L!EcL z5~opxxJ-3-n9{_n@?h3=FvX!iA2ia;H&L$dLYncEw9;lHLW2L~*K7NDyv>ei%9rlU z2)gd?bl{y8Y&im11f5xR_3c{hpXMo7nbz>M!8*V)1g!t5(*E3}fRVoK?}wmO#LK5* z)hBB~X$NQ;mJ)Oi*aJX~CM8tU)0q#Wou6zWd7R(fQ(<95fNA7aiD+z j$LA&LqTLH=my}^hgbmt^*zUF+MjZ%4qo>d n{KLiIU%S8d4{CNXHuxdYc#4LMtmRb|0Dy%6iN@2nI{qg> zKRxF6i1-_~^V?NkMPpOIw K-*0R0eLy47KL_U*FJ_jq*$^cc&X=&m%YgxO zC-?cWv3NZ0ANFF767Ymjf?uOOTh%bzaF3-iX%K0XHB;ho4@NUoRLmk?YhK6R4t^f` zc&dsL{@Dk#q`g{ips`0oE=!%x@W8*3-&2*SOb>JyZh)-vJSwB=-h7>4Lmw#78Wyc6 zaK&;R6G)#9g(wlp+3ESho%Nt(I{Tqm$gNacn@n9TTV2W+>c2k!6#tj78GJu3zJF2X zPk&&a4RA>YsAvUHh5pa{;Ym$@Jntz*{5`6D;}t66R}}$=RbbU}(ID9<$7|r^amtVT z;OrbP_@SiIh-5yRkQ8sX;(AfTFZykf$$VPo-LotX&>%?n`flNf_FrlvLN$wuPL| z%VnppNGVH)HKc`BoIfLzzMG}t1$FdiQ86}-@q%s6 ff#4+l>8*o5u@&TFv-< 4Q&oLQP1O&>f$wawNB9XVzqXHntkXUY7cK%Ew#H6)!4BF 6WiRTr10t_tKp| @oQN>XE;PkMFTsz|qm(+`!2ZFz@#5!ySJYO)~zBCY%EFK!T-Qb+(`sP2Co0V9N?6 zNG?U$wL}o}MK|jVNDv`r%wiOQRDGHHJ)D;tU_!>zeSzt2VF-%DFp`|j*bkyKBqQTk zsgfoSMKf*+LD>{-s%X4^L9iFlBo!VSGX kt5&?f*4wPu~3JG55`%Gorhn&bOe66ROWZ z)0nn!>BMW-;I;H=-R~1wjKgkEtg8?DeBB0#hO!vEQM%G|M0T6!o-;N#=r`%@-IbH1 z_L8z&wY@5y+epVmx{!5IkDTirv`a8{gwEVm8`rnA(kXX>7l>B#27xqmk74v35pHj5tL+B&M(TmB5` z_ZbqPy$|TGfrcTAsez@i)nRzL;K_yKNjai`h#qBqOF;?w;Rq}<#xePAy0fp0l;Mmr zG*FMuHq6V2KME(xNxC;Si26EY`c}An^l@^L2|x=LTzq-f`_)zQ1LJA5UF{f5$+ZA} z3V+=!d@rTgDwk74{`0fgREcNNa-;; JqK&6yCpzyklSGJWkCt3XvSco3YoAHd zur#!1UX*^l$HpBLvBQmNcjmzn9W^@aRo{G}zk^-9MtB|8*M3Y)zXkJGONH;KhW`YB z)f~|4^!mSo_s&$!}l7_>G*%cq8z{Z;LF hV(1b=8#)z2O4s3zZkWUpz~)#om36ZYX65V#bYL$39H}LTg12XFMplF|H2Tf3 z>%w;STZc^6JQoYZVFYVSOYV?k;d6s3jdTzyb;?+C9a*zQ<4%XMZ z++3;(bcLon4!yVXk}Il|@c1d`R~GbAjt6K$0$}j}Uv1|BPi6bYaU`-Op==Tg8D($T zdxns`_g>j#BqKs0GnEQiAu= +<=XYQC zb&uz|ugl Pfa7P&Q~&e;}wuDH(k$e6JpU$GRK1T7GK`^ z+od1ALV*>c!q-o{VZ?HnN1=Tw{m51HLeR3UuAyQLij#_xjJ4U!t@1l;^c}txlU#ko z;Rj@vx>{+9B$t&k9<3h{7w6IObN>=6OWAyEgl=gF*l701>~lf03+I=+ZPEYe>7)D1 z>$Y}nqgtB--?lq-V$D;7OjsdH`M;YVi@7*C+t~mS)thUcb4^e`iPY{@MI%|cjDC{l zP{8WCaHIuBe{5{Y5%O X zI}SdUMv5^cGQMr143a~Yy`~Z0-<%me$|bBLKPV-6Tf%x0*>w%HMY(e<-`l>{)|CI( z34W;^&pXHtq8ISlTTDINkDfO%HE^+ZhRqNqEUW>qC}wV8v6* 6aDT{c1z@NT0x$L zxrOOo_i07>$6LWB&}@j%*w{lMEfppcNWsY*1RP6*u&juid&WCkcyBM0;Lu&1Eq059 z-CMZyufJ@wnRaQ%N|Etgsv=~Dc@g+PLd*BDQiND1lpS^l_~{&UnnKr7f#U}RCRGcn z8l#-4#y)GU{~$qsLE4@SvPl$>oA>xZ^5F;*%bw+?X7@&Q>uj_ScYL~nlgg(h_$rw4 zse}3^<>@{S=hp{t56_qj{-71KTia#Mytgv~PJq#y*f|3SFLsEtieJQ;4v5kOe8l!9 z&h}4=+1UVre?WjCR0x5WP(Oq= eetH_;OxjgtJPM v#|udp)vbdcu~9$#F8RZ&p|UjMejc%E>IDx&zK1r}K`i%bE>P4u zzml=M{i@(##A4GI+7`L0aY^tHReUW} 04|6V`(=)HjvlO@cYQHzQ zv`MtcutdJ-@)}MHGf6wJf=X(jDji11f-6(`=DR+(!Ogqcb*X5{u{D B5iAI;%>4T87B|{%@-!T>Mbte(u5b HT)Tx7pi!`7MXHIsQ+F`m0O?C`_0GwuZ#IS9^p0 z7>lBzrHPTVI^an+!|_<0-0VN~RVdW45~2;Mnx?619m8(r&VCo75Q=f48@ae*w4B<8 zAgAPo_#qOH!+HnM1`nhjyh4PUe&F=-M%Jaq=7LH|9)nCT=h~GoxE|-eE*Hrd7r8ED zsjPN044+DP%2jnWSV8=Gw!({Vwh>r?&9iMBDfd6{l7u=PUi%p5;_ourj>gI5K=e6M zswbGUu1$Avtlytgx$MYQ-DK2H_GwwuYuV!sHmFD@vMHY?G%_`99^@6odqNiA`ah7W zzdL#a&wA@73Z7B0%2oq(8QK8;;`fEt^Zct9=U? AWIRXDS64FF34qHp30fFpE(dnzv*P733zNo@ZEVRntZYy?pKyq%f z0f|@0r9tg$!8HyOZK3{8?;3cIG+%uk)@1fnpj$e_*uh+4shY&W v`tJ11sONj9>YBo;1-m&CYe>pzAD(l%bq4G|r>iIz=pFtmlaD1CDN z#YwiclmvEh6ShDZ6Z7wP3>Z5Pa&qV 5 zHk!y$i)!d{c=myNR6Q>qM~h}3vid;uBiXN3Zs=llI|sdaSsD(V!1$+NDjlj%LRgbB z-z4)&_41AkGt&39dWl!QSz(bhn;dQa!k0l_a#{JHHWu2(y)5eHLY&Z8^K3&2W2}Oe zAFVi@$z5_@Hd)s#2M^{tl@g&HJ9+)}F`nFasa81B7OO V}QhNqm8V$Yt-t0yyfbitgEF@T^=Mf*`MUBIzALPy3Fd!MN! z7Qws3jblsX2V%Oym~p7bC72u@w&|+yXpuFtv2{JKd8mmY6sHqQ-uUW@P=sUYS2}*v zl&iTJLyPU5gE4s{Un<4M4;&j=u)p~c+kd`9jpNEmVn$LLGR>T3R!xoI>vTQtbm5qk zg}%qj4b5$pYu=YTx-xzbicjvoblGvJh_D`G@KW;H=&bz#TUA3ZOKn9D_iB`cyE=t; zi4%IRioPCMN%|t^!4~7=E5huG()5@e%OFUe_RSIZ(9zI%N#}Rx%3BzG)mS23hO6%k zI+ckwe2?Ysi&^rv8vLB^K~9&2#xdA2{@`HX(f9zem-jl3va6lBrf} {pLXgJ>C~I24D}_iF4JU(eYBI_2zzXxe0Ie9#t(0~ zH_Ipd*6AauQN20_1dk3Z_x4M>-&5Dq->}z~azPhzlFl ZXAmaP-(j0GZ$OSY^RvEyu6l z%Cd }IAq1SJcKE-8(&k6DovoI&LpB)Qs5}aHW zTH$HLMP8wpKZH+AD~lG8-=Q>wAy?*}FDh>SP>trgA6D?}0&NL{DXnaEbU+T%#Y6nL z58Fr++;wad?>bqMVVIGg?B{;N+J|$M=ML$euPs5}t{*`&WUI6wS8h* DWb}YjWUrGcagLJ-*~IvRh_H7wkshuwH@aMTXLa9~m3Wo(pX FyLMi1H_-3r~%z}fjV^1{p iZsLICc;mCoNQ4!HF&BXeYGtA DQQ48zg l!HQD_C9l#^*jM zoGIWf(R29_MYrrPozX-)#fs0%8to*x8ig_;8z*&%ioTXJNv=kjWWb=|E&1!=Fu_un zk3`6Ch?_VWKd*DAu=u`pSYaPMT|8exb1^P@WMTTo$_;)(il>#Kky?~^HD8E7qV=~^ zo2f5*&a2)*yX>%>6Wc*V=Xv@R`r}>=qepYjbT~|nF0*2 82ghUb1FGW+mD!AWfVvJz9Xa&$Q=M0Y{c>&%NYdwY%#ZgN zn#-b&s__Vrw9XwK8xa-#F+^ghZ1Ul&iex+cQGW(fa!s6O0cr1b8`198@rCM@B`0!S zmN~O1;d4j452O I!`%6zD`+u$o=re%7n_y0;k36f-Fk mB6#s&65$F-fmJUfz_E-{^MsTH7a)4-%+&xewT; zwS)6->Ew$l7V^cNw=|txie+WEb-gKhkUi30HbLTuDsp1Ms&S9Q8ZlG$wJ(}M?_!2R zCn|ACWRbPY+dEfqyKHr5e6mXEE-us2D%QrFCy#%ZAp5{2aq>WgFgvE*lk6eEh^vv) zQ^9q&Q##KqkQ`r1tZejT)oal4GDDuu3ec$Wl*4%0km}Wgdg-X}Gvqr?;i5$GB{^&K z=PWh`i7ZVONvgBOi9g$)TW3be^?a}Rc(VDk^vu(sv$9C>2GMm51^2J@fRcOf+ PK|*z<20TG(zLv)%l`+8rtW1^4%Z2;+S+v>a4)gP*Gi0A}Zly4T}* z(D$PdSkP}vnm7Z~Odx8{3_8^;j(q_bA-?$P56s+L<59}H(E;5`nuJOir|S(r%BiR} za+Y-64_}<)X1u&`H(I80zSe0nwTzZ@2#X=c@*=z010@rK^xF6mtdiPew=m*4lw`c- zMRTdCGuZn)zoN TWRYhj`tgO{7D={=pm9nU_FqvZy>84Uy=-!l^K#Xz*^?ISGo{@h z-dj2Qpga%4pb@RR*Z#^yA F0vMF;|*gdnF=#w HSWcNe4PA7OUo Fy#2867Z4vYa0MJ z612ny>meYDB}X6FoE$~L6*+Aqb@b5dWrKIRbG{1Msz7E+caPAL E3w@lu{WIK2cPU7gPYi+5!f)x4lS2#sy?6G1o8v z0tR6jH6x>HAlvZg=hnk17CI@1P{PRg^$*<}EY0u6ENas}QNK24C>7^UHE7ClrjUc; zKG9i6N$q$1$8t#nC?q8xk|<-CSTk2^6H}3x89kSqBR}X9%omFLK(EYj?tZ!?Y5p~? zvIlisC+Va5^}~=qEv}t;V4rgDj*TXY1@=;`v2w@#NQO@KHjbotz8l^unYX=_Q?D&X zn{h1Zwti1Dy-sghb=M!$19{jHjXjNb4DWgBj$JWjv(b!UK%FOmI(y4@f>0ehC%C>B z8Pf(C02=2!Fe&?i!VRJy4dRECG;GF$xSu>Et?fKv{PK=PU&VEHwv)I=1sh(z^7ZwY zal3m+N6B$6u*4FZyQ3pGg VqXviVt<|uvK_o}H5OW l zdOl4`)_%Ulk;J^-oaAMKs7}?6W%6iMzB^=tE$W|$`E%!0)XsP_7AG>A%@z<-eDWjV z@f305rV-T7?bPB_PtentwUuZiFvZTX1`Jl7LMakK&m?W$m~VcCJ&M6^X=uliB(`{@ z?&^h+>t!SQA<@bHaZ }txT z>8|`Wp?xD|L*grAM3+@RT2O@_l6Fc#m*}-Fs>6^WWY+n3S`hr6C_cttCH1o1RTe<9 zBfwfg`LEr|)`s==5D*z74|zWsyviFq-{PNvl^B5KU_opl_K`*AgCR{6`MHv;7DrLm zDle1C92&Me+F?m^k%OcbLl+sMPpRf>lTt^#6Atz=DY1^SDj{MwqRM`(9~^s_I-|2i z^D7RYK3!Ao^y9J-`QT1NB6&yYFo7EsXB7fZ7coTPZD3uzuQFeEHaUQ?T`#d Dkd( zcVun8OXs<)Bs(;pJZ|=<)g4w0EEYg6qU%oK>d#5*%symxwvOy(XD3x?Q~R}7jJ3jn z1E?H|%U{INjZz p zP6-p7Xa2m4Sld%Vi2L;Pe)rlNXp~^!1M&NNo0LE&5}>ge);QY-hL1i6;uon1k=hI8 z7SO4fhQf;KZ{Ce2LibyV$YQ1nUe 3RY-@6JXk{~h=?$& z)69dEVJej?z`JVuNgo1c7-}e}LJF8+7%=($`+?%;lLE;@3~ZqD5TN_o1p-Fswt*Re zK@{O?LalPIoBC)*k?M^DxMR=XbiWD9VA!Y($%@N3)Ke$D@@;T{&rNAIUF5!V-B$&t zx829J`A=1vNiu2Hm^28>m{i--(TckFGulkillfspx5=3(-Y;U72~;m?p+iAu5^{Nh zYih2fhrWR&8$UE|LH<&N%lzcT;6!#Ocd0e%l^ ^-KEl-x#cqgK)DN}VCO^u87U z7KO0p@&RoF91W6>9*4EcB_{H0%$;AUGtl9m=wU(Y)odflWR@@<9J}MqL_0I~a;8;{ zImCeb^_2p_!8m1?z%yf{+wIT@%m%$@?(&xbmgWnz#lL8W%`=Z6H@X>Q1DtrIIGS~4 z$fOC)Q>pPTdemDYSr=KEUY_Uk11X1i`LXS!f8bS}zzruQiJOVEOfEHMp|G5J^Iq*r zSqpPKW+=CCzMK`c^`|%J9>IKfF`ML6jyiR`mO_5 WBB z$uml6t@#deIJQq_#;GVqpaC|BC-{4NLk1bR5oXCS46<8>L`p*5LGSH-J?JNY+P#>8 zwY8yvkrmYRaay;+t|70UKcLyRa|WNK(Ptb6W_Nae(@pfI{-!+FY_*-(pBkUz8zl(W zQM+$Py(}$$EINerai9jVY!k|rcd8+CnwaaP{KGpV<%_M{>LAdN_nxxAXKz00rxviB z9^&rMV*)>O+> Y$>^?u3U-uhoq09hX<|A1zqV5 z4kLY4^{R?s9OJ0}l8pU_Vs)=5v-` 0Z-z@1k#;$XD`|AZAV1!JMBj z?^*~9BTTk$zDiBY${kX6<`~MC{tx(GbO$^~1&>{49OBBRJo&16;RT16ff~D=3?*B# zKdQECv)3F|(XpzWnza@VR-ez*0#qepjKy~0m*lWGWAq>Ko^VOe?Wvuq5fN)d?-;dj zu8~aVpWbK*LBlSRd@9^BeRt^-cRTwv$-o1Iv4b4XRy*_)-Ypm2oj(5&x$|~rg(!m+ zhGObv;*Olm`oi=pD3|Ke*~*`!P&Q)wxwaJB3d&5CyDVp2PsctNZ#=JIT)Hwzlz5;I zzju)NZdMZI^2CR)`t8S`Vf!ApkhvLug?`j8PcD)3{ZX^@_yB@J)$h0u@OTdQ(bcbH zS|$Ei3DjLLJ7O*$tUY4yFJsd+{;J#FMYfQl8|V6r1_35x@n>-x6#iN6c1y2}&$c~k z@ltUsY!{d+TJklTEf+F^jBan3r92S*e#N<*<=AYklGxFh3M UAvCHJ)1L z7H#-+2?q%9Ws*OfmS0~VjK`Obe|bmoHG1ka`3kMR-Q?R1igxvD14Xn}&s$b<<1Axd znTg?b#8WY+XK*4b=H?GK&@h!*SzJLme7YJvNwRcywxD^2YrW? pJUU9YT+Sko$SAkaR3QN<7a;;ZL$?ChgI^q$e4M3-y980E+){1jUf%Y8 (xZaciT4jfuSVbuhfrbJ_ z(#TY0{kJzu?M;@8=T1+zMm{|pJ}6-A6Yu?Pm$TNKd-sl@YEavuUFTM{l8u4i$^kxm zpA`au%g_o_7l7gf+vVYa$jz;+i#8DjkPs8pZ*s#JkCBcV9 g8aF8J@N@D4K=eN$w9Avhfx6VIq4l9UA$GtT$f2E`6v%BZz0MkYzp`*u> zj}ZO18z-L7^)Q<5W_&YYWRGLr8WYpdO)le==cn?*8-%$zR!J@V>&IF=aywhxlyW20 z9u*=xTn~14 %)idmzYkmrm4M%)TS0hKZZR>m}XS_4)wYYM$*k(Y~9mD@j8B*iRa#+ z2oTV{iFYKta0PARghX!qV&_t>z%!|i{9du-IVpUalqa1TS!MC3-;j7ESX0Ss#IM|s zdwqM=(>$X>kE`yYk605sBovDl+*;NwjyJeIGKM7WzyycE3Vnh-M)|yd3@YpzO1! zTqDtyNd@_tW|pUJ)qGJ^7zpt4=0LuE_4LCq1M{}|gGUw!c%)*2Xolp^OWsh+=48LZ ze G2IL0 z?jvL(i1ncF))^aOAU`7F(|yOrgH$Cl%O>tMjrh6aCwOQDZlQ@QG>HqJw~&{xj1Z4( z6KC>zudYw56zYangcc#&*4bKne4|o6KjbE#o w zMBXM?5_!VoF2UlAYh#R^1_f*(hLtjFGr^Z4MQJUtIV=!@)BvGU$8fjgi#)N}@5g zm!$de4pU^Gt2tjAC$BuT;w4^)C>DRg&g2!mLE}tPDlQ|w#C w+ojgpoAlv#DgW%%q zXrb8;;rfhL{m@X9$In=)gKs3_Gg3(xX4lkK6X2l*(z#HXGE!W=l^dXaZCtYKM)V1y^H@{LG(yVjdYpso1?>#G_&j+nto$zw`(8 DOS^sK@*3(Idk2w$W1VWigK3po02td vtyv;M_=j$3r+6>1bn zl~|NQ?z6oX7{6(eW K4N5Pv54oH|< zMG*qUdf#sg%}l(z>3Z+`t=ErmT$BZm<%_ZNzFu@bU-yG0Yj|J-XR-nHR *2bJWzGOS^8cEEakd2o>$=m0yaOz>J z_uQ0^;MY@M*hdDeFZ$*Ax;eR-nhZ}AJU!>ctfiByJw*EP<;z@}YT4XxkpefWjt&~} zis$?wM+-n!%&8#N)bu)nGCOvBMBn={YKq%QE#7Blu_SGz1{D#Z0@*`Eb-{%!uaAs- zcv7*9`4v)m*`(N!k%|k-CM&Kh6jIYKiM@0f!^WY=;)xa|>aJE !W8L0&?#|ODl|s-qWAEL}D*@$;h#<#$W5Uv2vp7T}keIdIQtCG^$kbJGoCE z$ustZlFi{J NMT(}f+_*j6*GPTx;nmq2Dh=gW~T!Rd+G70&{nmW?^ zMvT`+-E-#-QOq2SCQCzq@g)&YjO^Z58cNaJs#Tp^OgD~LPvv) j1B;HXfAc7D9bIGmoS^1FX0y(A;`PX^!BY+9tW-mbpc{$8vmrgj z!>co<)Lz(l#A3ZEhG9OZAANqOJaUKv>xB@7Lo@MImyuOdHr^r1Bg9kD?W@^i0xHzD zxOF-M#;+yDBflmYC|~`WUd7Ih-R`SHBfX(R &5GSB$`GEqAQ_tnCm?0Di8oq3h}%4SA=2 zPJDq|&kP&s`7)KR@qS8w>HMJYrRGCgHzv9yGHOT0nvg?;NX%C3U*`r6iu9dTUma~v z`ig%G)pfP9^xKVNRnkK^$ySBMKus%FUY=u> 5|pLJeaX~+U5h#kMT}a z(XryzeCI?l^$Bya_BWx{!EMb*f|)KV39K@TL!oELo@dsLswx!p9DUG0B=afVQHm!k zQ1lcq{ Rz>Yjrx$` za@~?Yg)*krKd#i1Ffi}w=);quTSfPVZASH8J5T @2jZ)i~e%b5z;WI#dJaF|LdrlQX?CzvRxDgu~sO(ER=5 zL>F1rRXe-Jt}kPbXk4ZCJ{*;at@K=~o?nqhKU`RIVXq`XiZxY3r;Ic~Lsfm8nuxw& z8cKdB_z^WvU=owDM0bk`-Hk)C>wYCCB=p-KWsg?zq9i&Ur|a#+qSX8B?vQQ}8~t)M zIW$for6BHJzF{P(&0H4_OQ%cr)3dc-yG~Gt_IW-zf=Bc4soID7%%gh!T|YX`NtaB1 zmNZBu_>|k++G&wZSUSI4e9J{JWK|1zhu2!iWxzVYU-0;ZdvF>?TR}8kx$rYO!sloy z=rJbg=x8>CFY-_;OZev`t|xHQtqL1g5!}CBo6Wkw*RC&^j&s-Sv*K2Oj0E7Z=VB zaunfVuG^58Sn+!i4Sbgkb`7J`u~5VOw3Jy;nHl)rd3-=S=m7m&@66?I&V)_Ycbd z;CO8_u8CVk*NlwiEIeE~b(&OyXJp3BrO`FTLnFo7`%04A*huVpYo1hVbn_uwj0R!p zXS$;c^fB&EMKOtmICT kI6+B{T&wL4IJCe=c^_*jxcrjrY$j|lab$Ei66smJuDmBd^fxV zt-MvHj<$hL>SGH7vpfHjZdr1)!<^?%bnzI_9+?p)q7oQ8uB(3~YAm;jRtb+#`0VBT zT=kqQlj3$OrFZCMBG^0c`9H^HjTtmAWUt_+Rjw|4Ue_!#sZBekjN1C-M|?6l^T6$J z>HDL7Ys}1IlN*Yb?1N)N%~S8Q>6Nwh9?tQV_Afa5(&bL|rw~of+9j13_2DXt-1b~D z S}BtnJ^vC?xx@0QxtHjp&nB5a@^m){nY^bA z#JzsO7{hMG(y4}__xw^>f3=-FU;ly-Sz;-rtiphU+}i4M&KCu`x$>B}Q%R!SIUJ1q z6Df}aFHc8BYWr3%J;I;+ntMkh-K4Gf)%15KnfHmK2aR7FCwvEvDj}m#A|c*9Btkj? zEaD*l;BQ`jvwen%L *m#q(8nuQC_10~%j0d{YiG3dDY$!m-1S~NDB$khv_ggqk+hzi9D0SZ z?AC;=aQ9{h9(Knan9s4Wvv8b&Jm-$ToG~;puyukXfZY-H^sw){5pcs1`0azh6iCft z=jgEql5I0%a}Ihd3AC^r@R8agp#r|W{tFUFC25IGD(Gknwh)*#>Rd8}b^v&dw#iPd zV#R3#9FSvhYH(zMhxgt}FpZ=vOdJgyjm#~K46Ofzl1$oYSIVc;Ss{>@|4P6|9*$Cx zVE>fTwieE?#7bLLxe ;PJ7>5a5PDn#3E~ZIi)>!u?F_Nk k2QhGQwgdJIH;)|uHI@P#R@cA6+B&2M zHgT9tdy!28j!)?5zS|F8wk rFb1C4h--Rt4h?KE^i=%1}iNh5I`Z8#H{g*L}H|ZnC$jM%0 z7%=R*TbG6(=W|K+tlO3qf?IHR>qG42gtsJvv>@`a{gdq0AByUB?+?M<*Ww_NF&)6i zWQ&Lj_;%?Z5P@3n=gX0wec)$um0y|^))f@IOc8X2&29{_J-YRS7BK)MB+0QX9Jbl< zJqx*QZ2% nfn8x(JAK96Ox9V*tF}epBe5k374Qw4Y(J8K za1hSSU`-{i>ICIeW!}8* _cSJY{0`+dc?E3+1!^>I|So0w4HUtsv!JqKUf#C)AZ( z46QAk%zvKK23sABkU-IVS3 +Je+t`RQn;62Ws~jn3 WN@6YHs;MSzuQf z8`!>O)g4rrq$9Py0(X`L=3|muEPxJ3Hh;w;Vr^mIB=Iu_3Jiwy?LB?(@m^#Y80@+U zhIpvv*aCxy)Jn5wkxXn|5zhMmYji%~c2rLWxFuw5E3*Yb1$-l?-4}$ot*eEjovjVf zy~GUcx6S~-3G{QHikQRdw`RQaun+tw_iFEKK)F^h!>d_c=>FAz>670e#EK8RoG3rE zbKis#*c1HoRU@pltF*1Xi!*Qt%*4RvKRS7*m^A`*w$~Z9r%aaXfVhcSBFI5B*ajt} z<>DFkLk0x*?e29rO>B1sf-n6G1}bL#8z4|2U~mh =9_TZCmgKe!-kK+4rm& zG`{=y<$e+IrPEj ^E-~u>EtWZLbAi<`|GC;WEz8Vj{s_t)#-hydiKGt{s&zn;ePC^ZyzoPwF zmKMykd$VpU2w%JwtYFDMVcM;fEtnUkp+uhxf-bSm3tvDM%saySFEmu$wVW&>V&r;P z1jL9t4*b%rjc+f+=0Iv(gkx6J$Ib0HSd;HI9pZ#DyW{+O2Kad{1S|yXzKA)tn-0F- zEV$`x_sy|+xC4?66n0YOx6amN`Z9uc+Qx$~{0hbk64 O z;Z!hBH;`)J&wU%zZXi71GOCDx!V=tf1%j`M3I+-X(qilb2vi{z5s!+t$F4l^Wlh06 z7lil41N41ZkyAuGSXQ39^1v691oJqE?V0E2p7wv(P_LTTu1N5u5y42DK$V(3HW{pk z{RdOTRc{1Lf8r07fPJ+;XM$E01h> zC!rxkuq!OI2s~=;gxKC}fh;&p_6Gs+!?2=hV4$!CIw;VcM-0%b z8w&;W3T`9F>nZrsYT!1i{d*wD_^?Z%H83yiHHaz6rjRiH-2{Y>zIYFu=iEmtLTaCZ zL8@4ophCf}uom4Z9DIc|uyALAJ -#t2`L4QyEC zO8aW3pAvzv;LC{sRZS4qX9F7<%*h@Z@<-rgx8dOnse$2XmH#okDuR-7h>+jS2keRr zUl9$Aj02p||MQN#)h~Y4`%h70`T}=Fg)epnMjiOqsIYg!yX-Tm)dxXoVTKL%kdRGk z_^M@K+FI3rEbKNdxP}=bYdHKU7y&e52ethc8!|xGtNkNr$RSoR8caW!zk+?=sV97K zGO(Vf)&C(H%sIfvzo>vqm4WGDp5b*u$R7)<0=Hja2;`IaC*2Gp>{bOYFe6OS|56yf zt{7P1&i@M|WPSwm4Zq!C(2EWIV`0_dgq;Q=dH nETK^|Zm?dv|hd-&B1%5D?VIT1hL!kfmkOyDM3jAR4KsfsUMSs|XLF{MS_5Id@ zl=ZLf`ULO=q`*%wbn*ZE1iO@zLev(74w1V)34E<4@RJDX{-2)Yk1IDJdY%{B*LHm# z_)16M=jqV (;0uk4BpUx^5eJa*}CkayH2LWHKU;s-^8EdX{~0l}{w zOZE4K_Pde%zkDHl6(X=k*arIs`Hz}Jhy-(wjoP&>;cE+lIS2R82^;Ws7EM9~$n71C z01)wPjUR68OETQIkkI_fU>ewvhPYV~(`*OvLFSH2e@O$Y5QK