Update spotbugs and related new warnings (fixes #157)

pull/168/head
Richard Körber 2024-05-10 16:07:41 +02:00
parent 57ec36054a
commit aeff12088f
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
18 changed files with 108 additions and 94 deletions

View File

@ -24,6 +24,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.connector.ResourceIterator; import org.shredzone.acme4j.connector.ResourceIterator;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -285,6 +286,7 @@ public class Account extends AcmeJsonResource {
* sure that they are valid according to the RFC. It is recommended to use * sure that they are valid according to the RFC. It is recommended to use
* the {@code addContact()} methods below to add new contacts to the list. * the {@code addContact()} methods below to add new contacts to the list.
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public List<URI> getContacts() { public List<URI> getContacts() {
return editContacts; return editContacts;
} }

View File

@ -70,7 +70,7 @@ public abstract class AcmeJsonResource extends AcmeResource {
throw new AcmeLazyLoadingException(this, ex); throw new AcmeLazyLoadingException(this, ex);
} }
} }
return data; return Objects.requireNonNull(data);
} }
/** /**

View File

@ -87,4 +87,9 @@ public abstract class AcmeResource implements Serializable {
return location; return location;
} }
@Override
protected final void finalize() {
// CT_CONSTRUCTOR_THROW: Prevents finalizer attack
}
} }

View File

@ -15,6 +15,7 @@ package org.shredzone.acme4j;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toUnmodifiableList; import static java.util.stream.Collectors.toUnmodifiableList;
import static org.shredzone.acme4j.toolbox.AcmeUtils.base64UrlEncode; import static org.shredzone.acme4j.toolbox.AcmeUtils.base64UrlEncode;
import static org.shredzone.acme4j.toolbox.AcmeUtils.getRenewalUniqueIdentifier; import static org.shredzone.acme4j.toolbox.AcmeUtils.getRenewalUniqueIdentifier;
@ -33,6 +34,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509CertificateHolder;
@ -132,9 +134,9 @@ public class Certificate extends AcmeResource {
var login = getLogin(); var login = getLogin();
alternateCerts = getAlternates().stream() alternateCerts = getAlternates().stream()
.map(login::bindCertificate) .map(login::bindCertificate)
.collect(toUnmodifiableList()); .collect(toList());
} }
return alternateCerts; return unmodifiableList(alternateCerts);
} }
/** /**
@ -274,6 +276,7 @@ public class Certificate extends AcmeResource {
* @throws AcmeNotSupportedException if the CA does not support renewal information. * @throws AcmeNotSupportedException if the CA does not support renewal information.
* @since 3.0.0 * @since 3.0.0
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public RenewalInfo getRenewalInfo() { public RenewalInfo getRenewalInfo() {
if (renewalInfo == null) { if (renewalInfo == null) {
renewalInfo = getRenewalInfoLocation() renewalInfo = getRenewalInfoLocation()

View File

@ -256,4 +256,9 @@ public class Identifier implements Serializable {
return content.hashCode(); return content.hashCode();
} }
@Override
protected final void finalize() {
// CT_CONSTRUCTOR_THROW: Prevents finalizer attack
}
} }

View File

@ -22,6 +22,7 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Objects; import java.util.Objects;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -74,6 +75,7 @@ public class Login {
/** /**
* Gets the {@link Session} that is used. * Gets the {@link Session} that is used.
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public Session getSession() { public Session getSession() {
return session; return session;
} }
@ -97,6 +99,7 @@ public class Login {
* *
* @return {@link Account} bound to the login * @return {@link Account} bound to the login
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public Account getAccount() { public Account getAccount() {
return account; return account;
} }

View File

@ -13,6 +13,8 @@
*/ */
package org.shredzone.acme4j; package org.shredzone.acme4j;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toUnmodifiableList; import static java.util.stream.Collectors.toUnmodifiableList;
import java.io.IOException; import java.io.IOException;
@ -25,6 +27,7 @@ import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNotSupportedException; import org.shredzone.acme4j.exception.AcmeNotSupportedException;
@ -114,9 +117,9 @@ public class Order extends AcmeJsonResource {
.stream() .stream()
.map(Value::asURL) .map(Value::asURL)
.map(login::bindAuthorization) .map(login::bindAuthorization)
.collect(toUnmodifiableList()); .collect(toList());
} }
return authorizations; return unmodifiableList(authorizations);
} }
/** /**
@ -135,6 +138,7 @@ public class Order extends AcmeJsonResource {
* if the order is not ready yet. You must finalize the order first, and wait * if the order is not ready yet. You must finalize the order first, and wait
* for the status to become {@link Status#VALID}. * for the status to become {@link Status#VALID}.
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public Certificate getCertificate() { public Certificate getCertificate() {
if (certificate == null) { if (certificate == null) {
certificate = getJSON().get("certificate") certificate = getJSON().get("certificate")
@ -154,6 +158,7 @@ public class Order extends AcmeJsonResource {
* for the status to become {@link Status#VALID}. It is also thrown if the * for the status to become {@link Status#VALID}. It is also thrown if the
* order has been {@link Status#CANCELED}. * order has been {@link Status#CANCELED}.
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public Certificate getAutoRenewalCertificate() { public Certificate getAutoRenewalCertificate() {
if (autoRenewalCertificate == null) { if (autoRenewalCertificate == null) {
autoRenewalCertificate = getJSON().get("star-certificate") autoRenewalCertificate = getJSON().get("star-certificate")

View File

@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.NetworkSettings; import org.shredzone.acme4j.connector.NetworkSettings;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
@ -200,6 +201,7 @@ public class Session {
* @return {@link NetworkSettings} * @return {@link NetworkSettings}
* @since 2.8 * @since 2.8
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // behavior is intended
public NetworkSettings networkSettings() { public NetworkSettings networkSettings() {
return networkSettings; return networkSettings;
} }
@ -368,4 +370,9 @@ public class Session {
resourceMap.set(map); resourceMap.set(map);
} }
@Override
protected final void finalize() {
// CT_CONSTRUCTOR_THROW: Prevents finalizer attack
}
} }

View File

@ -19,6 +19,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.util.Properties; import java.util.Properties;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
@ -62,6 +63,7 @@ public class HttpConnector {
* Creates a new {@link HttpConnector} that is using the given * Creates a new {@link HttpConnector} that is using the given
* {@link NetworkSettings}. * {@link NetworkSettings}.
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP2") // behavior is intended
public HttpConnector(NetworkSettings networkSettings) { public HttpConnector(NetworkSettings networkSettings) {
this.networkSettings = networkSettings; this.networkSettings = networkSettings;
} }

View File

@ -13,12 +13,13 @@
*/ */
package org.shredzone.acme4j.connector; package org.shredzone.acme4j.connector;
import static java.util.Objects.requireNonNull;
import java.net.URL; import java.net.URL;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
@ -58,10 +59,10 @@ public class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
* {@link Login} and {@link URL}. * {@link Login} and {@link URL}.
*/ */
public ResourceIterator(Login login, String field, @Nullable URL start, BiFunction<Login, URL, T> creator) { public ResourceIterator(Login login, String field, @Nullable URL start, BiFunction<Login, URL, T> creator) {
this.login = Objects.requireNonNull(login, "login"); this.login = requireNonNull(login, "login");
this.field = Objects.requireNonNull(field, "field"); this.field = requireNonNull(field, "field");
this.nextUrl = start; this.nextUrl = start;
this.creator = Objects.requireNonNull(creator, "creator"); this.creator = requireNonNull(creator, "creator");
} }
/** /**
@ -141,7 +142,7 @@ public class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
private void readAndQueue() throws AcmeException { private void readAndQueue() throws AcmeException {
var session = login.getSession(); var session = login.getSession();
try (var conn = session.connect()) { try (var conn = session.connect()) {
conn.sendSignedPostAsGetRequest(nextUrl, login); conn.sendSignedPostAsGetRequest(requireNonNull(nextUrl), login);
fillUrlList(conn.readJsonResponse()); fillUrlList(conn.readJsonResponse());
nextUrl = conn.getLinks("next").stream().findFirst().orElse(null); nextUrl = conn.getLinks("next").stream().findFirst().orElse(null);

View File

@ -49,8 +49,7 @@ public class AcmeRateLimitedException extends AcmeServerException {
@Nullable Collection<URL> documents) { @Nullable Collection<URL> documents) {
super(problem); super(problem);
this.retryAfter = retryAfter; this.retryAfter = retryAfter;
this.documents = this.documents = documents != null ? documents : Collections.emptyList();
documents != null ? Collections.unmodifiableCollection(documents) : Collections.emptyList();
} }
/** /**
@ -66,7 +65,7 @@ public class AcmeRateLimitedException extends AcmeServerException {
* Empty if the server did not provide such URLs. * Empty if the server did not provide such URLs.
*/ */
public Collection<URL> getDocuments() { public Collection<URL> getDocuments() {
return documents; return Collections.unmodifiableCollection(documents);
} }
} }

View File

@ -21,11 +21,11 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.shredzone.acme4j.connector.HttpConnector; import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.connector.NetworkSettings; import org.shredzone.acme4j.connector.NetworkSettings;
@ -34,7 +34,7 @@ import org.shredzone.acme4j.connector.NetworkSettings;
* certificate. * certificate.
*/ */
public class PebbleHttpConnector extends HttpConnector { public class PebbleHttpConnector extends HttpConnector {
private static @Nullable SSLContext sslContext = null; private static final AtomicReference<SSLContext> SSL_CONTEXT_REF = new AtomicReference<>();
public PebbleHttpConnector(NetworkSettings settings) { public PebbleHttpConnector(NetworkSettings settings) {
super(settings); super(settings);
@ -51,8 +51,8 @@ public class PebbleHttpConnector extends HttpConnector {
* Lazily creates an {@link SSLContext} that exclusively accepts the Pebble * Lazily creates an {@link SSLContext} that exclusively accepts the Pebble
* certificate. * certificate.
*/ */
protected synchronized SSLContext createSSLContext() { protected SSLContext createSSLContext() {
if (sslContext == null) { if (SSL_CONTEXT_REF.get() == null) {
try (var in = getClass().getResourceAsStream("/org/shredzone/acme4j/provider/pebble/pebble.truststore")) { try (var in = getClass().getResourceAsStream("/org/shredzone/acme4j/provider/pebble/pebble.truststore")) {
var keystore = KeyStore.getInstance(KeyStore.getDefaultType()); var keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(in, "acme4j".toCharArray()); keystore.load(in, "acme4j".toCharArray());
@ -60,14 +60,15 @@ public class PebbleHttpConnector extends HttpConnector {
var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore); tmf.init(keystore);
sslContext = SSLContext.getInstance("TLS"); var sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null); sslContext.init(null, tmf.getTrustManagers(), null);
SSL_CONTEXT_REF.set(sslContext);
} catch (IOException | KeyStoreException | CertificateException } catch (IOException | KeyStoreException | CertificateException
| NoSuchAlgorithmException | KeyManagementException ex) { | NoSuchAlgorithmException | KeyManagementException ex) {
throw new RuntimeException("Could not create truststore", ex); throw new RuntimeException("Could not create truststore", ex);
} }
} }
return Objects.requireNonNull(sslContext); return Objects.requireNonNull(SSL_CONTEXT_REF.get());
} }
} }

View File

@ -79,7 +79,12 @@ public class SslComAcmeProvider extends AbstractAcmeProvider {
// by EAB, but the "externalAccountRequired" flag in the directory is set to // by EAB, but the "externalAccountRequired" flag in the directory is set to
// false. This patch reads the directory and forcefully sets the flag to true. // false. This patch reads the directory and forcefully sets the flag to true.
// The entire method can be removed once it is fixed on SSL.com side. // The entire method can be removed once it is fixed on SSL.com side.
var directory = super.directory(session, serverUri).toMap(); var superdirectory = super.directory(session, serverUri);
if (superdirectory == null) {
return null;
}
var directory = superdirectory.toMap();
var meta = directory.get("meta"); var meta = directory.get("meta");
if (meta instanceof Map) { if (meta instanceof Map) {
var metaMap = ((Map<String, Object>) meta); var metaMap = ((Map<String, Object>) meta);

View File

@ -21,8 +21,6 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
@ -44,7 +42,6 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jose4j.json.JsonUtil; import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Identifier; import org.shredzone.acme4j.Identifier;
@ -57,14 +54,12 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
* A model containing a JSON result. The content is immutable. * A model containing a JSON result. The content is immutable.
*/ */
public final class JSON implements Serializable { public final class JSON implements Serializable {
private static final long serialVersionUID = 3091273044605709204L; private static final long serialVersionUID = 418332625174149030L;
private static final JSON EMPTY_JSON = new JSON(new HashMap<>()); private static final JSON EMPTY_JSON = new JSON(new HashMap<>());
private final String path; private final String path;
private final Map<String, Object> data;
@SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
private transient Map<String, Object> data; // Must not be final for deserialization
/** /**
* Creates a new {@link JSON} root object. * Creates a new {@link JSON} root object.
@ -207,26 +202,6 @@ public final class JSON implements Serializable {
return Collections.unmodifiableMap(data); return Collections.unmodifiableMap(data);
} }
/**
* Serialize the data map in JSON.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(JsonUtil.toJson(data));
out.defaultWriteObject();
}
/**
* Deserialize the JSON representation of the data map.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
data = new HashMap<>(JsonUtil.parseJson(in.readUTF()));
in.defaultReadObject();
} catch (JoseException ex) {
throw new AcmeProtocolException("Cannot deserialize", ex);
}
}
/** /**
* Represents a JSON array. * Represents a JSON array.
*/ */
@ -369,20 +344,15 @@ public final class JSON implements Serializable {
* Returns the value as {@link String}. * Returns the value as {@link String}.
*/ */
public String asString() { public String asString() {
required(); return required().toString();
return val.toString();
} }
/** /**
* Returns the value as JSON object. * Returns the value as JSON object.
*/ */
@SuppressWarnings("unchecked")
public JSON asObject() { public JSON asObject() {
required(); return new JSON(path, (Map<String, Object>) required(Map.class));
try {
return new JSON(path, (Map<String, Object>) val);
} catch (ClassCastException ex) {
throw new AcmeProtocolException(path + ": expected an object", ex);
}
} }
/** /**
@ -391,9 +361,8 @@ public final class JSON implements Serializable {
* @since 2.8 * @since 2.8
*/ */
public JSON asEncodedObject() { public JSON asEncodedObject() {
required();
try { try {
var raw = AcmeUtils.base64UrlDecode(val.toString()); var raw = AcmeUtils.base64UrlDecode(asString());
return new JSON(path, JsonUtil.parseJson(new String(raw, UTF_8))); return new JSON(path, JsonUtil.parseJson(new String(raw, UTF_8)));
} catch (IllegalArgumentException | JoseException ex) { } catch (IllegalArgumentException | JoseException ex) {
throw new AcmeProtocolException(path + ": expected an encoded object", ex); throw new AcmeProtocolException(path + ": expected an encoded object", ex);
@ -407,7 +376,6 @@ public final class JSON implements Serializable {
* Base {@link URL} to resolve relative links against * Base {@link URL} to resolve relative links against
*/ */
public Problem asProblem(URL baseUrl) { public Problem asProblem(URL baseUrl) {
required();
return new Problem(asObject(), baseUrl); return new Problem(asObject(), baseUrl);
} }
@ -417,7 +385,6 @@ public final class JSON implements Serializable {
* @since 2.3 * @since 2.3
*/ */
public Identifier asIdentifier() { public Identifier asIdentifier() {
required();
return new Identifier(asObject()); return new Identifier(asObject());
} }
@ -427,6 +394,7 @@ public final class JSON implements Serializable {
* Unlike the other getters, this method returns an empty array if the value is * Unlike the other getters, this method returns an empty array if the value is
* not set. Use {@link #isPresent()} to find out if the value was actually set. * not set. Use {@link #isPresent()} to find out if the value was actually set.
*/ */
@SuppressWarnings("unchecked")
public Array asArray() { public Array asArray() {
if (val == null) { if (val == null) {
return new Array(path, Collections.emptyList()); return new Array(path, Collections.emptyList());
@ -443,33 +411,22 @@ public final class JSON implements Serializable {
* Returns the value as int. * Returns the value as int.
*/ */
public int asInt() { public int asInt() {
required(); return (required(Number.class)).intValue();
try {
return ((Number) val).intValue();
} catch (ClassCastException ex) {
throw new AcmeProtocolException(path + ": bad number " + val, ex);
}
} }
/** /**
* Returns the value as boolean. * Returns the value as boolean.
*/ */
public boolean asBoolean() { public boolean asBoolean() {
required(); return required(Boolean.class);
try {
return (Boolean) val;
} catch (ClassCastException ex) {
throw new AcmeProtocolException(path + ": bad boolean " + val, ex);
}
} }
/** /**
* Returns the value as {@link URI}. * Returns the value as {@link URI}.
*/ */
public URI asURI() { public URI asURI() {
required();
try { try {
return new URI(val.toString()); return new URI(asString());
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
throw new AcmeProtocolException(path + ": bad URI " + val, ex); throw new AcmeProtocolException(path + ": bad URI " + val, ex);
} }
@ -479,9 +436,8 @@ public final class JSON implements Serializable {
* Returns the value as {@link URL}. * Returns the value as {@link URL}.
*/ */
public URL asURL() { public URL asURL() {
required();
try { try {
return new URL(val.toString()); return new URL(asString());
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
throw new AcmeProtocolException(path + ": bad URL " + val, ex); throw new AcmeProtocolException(path + ": bad URL " + val, ex);
} }
@ -491,9 +447,8 @@ public final class JSON implements Serializable {
* Returns the value as {@link Instant}. * Returns the value as {@link Instant}.
*/ */
public Instant asInstant() { public Instant asInstant() {
required();
try { try {
return parseTimestamp(val.toString()); return parseTimestamp(asString());
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
throw new AcmeProtocolException(path + ": bad date " + val, ex); throw new AcmeProtocolException(path + ": bad date " + val, ex);
} }
@ -505,38 +460,52 @@ public final class JSON implements Serializable {
* @since 2.3 * @since 2.3
*/ */
public Duration asDuration() { public Duration asDuration() {
required(); return Duration.ofSeconds(required(Number.class).longValue());
try {
return Duration.ofSeconds(((Number) val).longValue());
} catch (ClassCastException ex) {
throw new AcmeProtocolException(path + ": bad duration " + val, ex);
}
} }
/** /**
* Returns the value as base64 decoded byte array. * Returns the value as base64 decoded byte array.
*/ */
public byte[] asBinary() { public byte[] asBinary() {
required(); return AcmeUtils.base64UrlDecode(asString());
return AcmeUtils.base64UrlDecode(val.toString());
} }
/** /**
* Returns the parsed {@link Status}. * Returns the parsed {@link Status}.
*/ */
public Status asStatus() { public Status asStatus() {
required(); return Status.parse(asString());
return Status.parse(val.toString());
} }
/** /**
* Checks if the value is present. An {@link AcmeProtocolException} is thrown if * Checks if the value is present. An {@link AcmeProtocolException} is thrown if
* the value is {@code null}. * the value is {@code null}.
*
* @return val that is guaranteed to be non-{@code null}
*/ */
private void required() { private Object required() {
if (!isPresent()) { if (val == null) {
throw new AcmeProtocolException(path + ": required, but not set"); throw new AcmeProtocolException(path + ": required, but not set");
} }
return val;
}
/**
* Checks if the value is present. An {@link AcmeProtocolException} is thrown if
* the value is {@code null} or is not of the expected type.
*
* @param type
* expected type
* @return val that is guaranteed to be non-{@code null}
*/
private <T> T required(Class<T> type) {
if (val == null) {
throw new AcmeProtocolException(path + ": required, but not set");
}
if (!type.isInstance(val)) {
throw new AcmeProtocolException(path + ": cannot convert to " + type.getSimpleName());
}
return type.cast(val);
} }
@Override @Override

View File

@ -13,6 +13,7 @@
*/ */
package org.shredzone.acme4j.smime.email; package org.shredzone.acme4j.smime.email;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import java.net.URL; import java.net.URL;
@ -208,14 +209,14 @@ public final class EmailProcessor {
* Returns the sender of the "challenge" email. * Returns the sender of the "challenge" email.
*/ */
public InternetAddress getSender() { public InternetAddress getSender() {
return sender; return (InternetAddress) sender.clone();
} }
/** /**
* Returns the recipient of the "challenge" email. * Returns the recipient of the "challenge" email.
*/ */
public InternetAddress getRecipient() { public InternetAddress getRecipient() {
return recipient; return (InternetAddress) recipient.clone();
} }
/** /**
@ -224,7 +225,7 @@ public final class EmailProcessor {
* Empty if there was no reply-to header, but never {@code null}. * Empty if there was no reply-to header, but never {@code null}.
*/ */
public Collection<InternetAddress> getReplyTo() { public Collection<InternetAddress> getReplyTo() {
return replyTo; return unmodifiableCollection(replyTo);
} }
/** /**

View File

@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.bouncycastle.i18n.ErrorBundle; import org.bouncycastle.i18n.ErrorBundle;
import org.bouncycastle.i18n.LocalizedException; import org.bouncycastle.i18n.LocalizedException;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -93,6 +94,7 @@ public class AcmeInvalidMessageException extends AcmeException {
* *
* @since 2.16 * @since 2.16
*/ */
@SuppressFBWarnings("EI_EXPOSE_REP") // errors is always an unmodifiable list
public List<ErrorBundle> getErrors() { public List<ErrorBundle> getErrors() {
return errors; return errors;
} }

View File

@ -80,7 +80,7 @@
<plugin> <plugin>
<groupId>com.github.spotbugs</groupId> <groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId> <artifactId>spotbugs-maven-plugin</artifactId>
<version>4.2.3</version> <version>4.8.5.0</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
@ -177,7 +177,7 @@
<dependency> <dependency>
<groupId>com.github.spotbugs</groupId> <groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId> <artifactId>spotbugs-annotations</artifactId>
<version>4.8.3</version> <version>4.8.5</version>
<optional>true</optional> <optional>true</optional>
<exclusions> <exclusions>
<exclusion> <exclusion>

View File

@ -2,6 +2,10 @@
This document will help you migrate your code to the latest _acme4j_ version. This document will help you migrate your code to the latest _acme4j_ version.
## Migration to Version 3.2.2
- This version is unable to deserialize resource objects that were serialized by a previous version using Java's serialization mechanism. This shouldn't be a problem, as [it was not allowed](usage/persistence.md#serialization) to share serialized data between different versions anyway.
## Migration to Version 3.2.0 ## Migration to Version 3.2.0
- Starting with this version, the `CSRBuilder` won't add the first domain as common name automatically. This permits the issuance of very long domain names, and should have no negative impact otherwise, as this field is usually ignored by CAs anyway. If you should encounter a problem here, you can use `CSRBuilder.setCommonName()` to set the first domain as common name manually. Discussion see [here](https://community.letsencrypt.org/t/questions-re-simplifying-issuance-for-very-long-domain-names/207925/11). - Starting with this version, the `CSRBuilder` won't add the first domain as common name automatically. This permits the issuance of very long domain names, and should have no negative impact otherwise, as this field is usually ignored by CAs anyway. If you should encounter a problem here, you can use `CSRBuilder.setCommonName()` to set the first domain as common name manually. Discussion see [here](https://community.letsencrypt.org/t/questions-re-simplifying-issuance-for-very-long-domain-names/207925/11).