diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java index 67b8119c..a2b8f4c6 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.util.ArrayList; @@ -26,7 +25,6 @@ import java.util.Map; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -194,8 +192,6 @@ public class Authorization extends AcmeResource { retryAfter); } } - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -213,8 +209,6 @@ public class Authorization extends AcmeResource { if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) { conn.throwAcmeException(); } - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java index 6280ed7e..fb66958f 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.security.cert.CertificateEncodingException; @@ -25,7 +24,6 @@ import java.util.List; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -106,8 +104,6 @@ public class Certificate extends AcmeResource { chainCertUri = conn.getLink("up"); cert = conn.readCertificate(); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } return cert; @@ -146,8 +142,6 @@ public class Certificate extends AcmeResource { certChain.add(conn.readCertificate()); link = conn.getLink("up"); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } if (link != null) { @@ -200,8 +194,6 @@ public class Certificate extends AcmeResource { } } catch (CertificateEncodingException ex) { throw new AcmeProtocolException("Invalid certificate", ex); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java index 70c5ac48..1479dcd0 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -35,7 +34,6 @@ import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.ResourceIterator; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -167,8 +165,6 @@ public class Registration extends AcmeResource { Map json = conn.readJsonResponse(); unmarshal(json, conn); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -204,8 +200,6 @@ public class Registration extends AcmeResource { Authorization auth = new Authorization(getSession(), conn.getLocation()); auth.unmarshalAuthorization(json); return auth; - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -268,8 +262,6 @@ public class Registration extends AcmeResource { URI chainCertUri = conn.getLink("up"); return new Certificate(getSession(), conn.getLocation(), chainCertUri, cert); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -321,8 +313,6 @@ public class Registration extends AcmeResource { } getSession().setKeyPair(newKeyPair); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } catch (JoseException ex) { throw new AcmeProtocolException("Cannot sign key-change", ex); } @@ -345,8 +335,6 @@ public class Registration extends AcmeResource { if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) { conn.throwAcmeException(); } - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -517,8 +505,6 @@ public class Registration extends AcmeResource { Map json = conn.readJsonResponse(); unmarshal(json, conn); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/RegistrationBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/RegistrationBuilder.java index ad871f6a..5143a85c 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/RegistrationBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/RegistrationBuilder.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.util.ArrayList; @@ -23,7 +22,6 @@ import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeConflictException; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.util.ClaimBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,8 +90,6 @@ public class RegistrationBuilder { URI tos = conn.getLink("terms-of-service"); return new Registration(session, location, tos); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java index 0ab16b87..cef8cdc6 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java @@ -29,7 +29,6 @@ import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -88,8 +87,6 @@ public class Challenge extends AcmeResource { } return (T) session.createChallenge(json); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -220,8 +217,6 @@ public class Challenge extends AcmeResource { } unmarshall(conn.readJsonResponse()); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } @@ -252,8 +247,6 @@ public class Challenge extends AcmeResource { retryAfter); } } - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java index ae42ab48..5ac1f487 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j.connector; -import java.io.IOException; import java.net.URI; import java.security.cert.X509Certificate; import java.util.Collection; @@ -39,7 +38,7 @@ public interface Connection extends AutoCloseable { * {@link Session} instance to be used for tracking * @return HTTP response code */ - int sendRequest(URI uri, Session session) throws IOException; + int sendRequest(URI uri, Session session) throws AcmeException; /** * Sends a signed POST request. @@ -52,21 +51,21 @@ public interface Connection extends AutoCloseable { * {@link Session} instance to be used for signing and tracking * @return HTTP response code */ - int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws IOException; + int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException; /** * Reads a server response as JSON data. * * @return Map containing the parsed JSON data */ - Map readJsonResponse() throws IOException; + Map readJsonResponse() throws AcmeException; /** * Reads a certificate. * * @return {@link X509Certificate} that was read. */ - X509Certificate readCertificate() throws IOException; + X509Certificate readCertificate() throws AcmeException; /** * Updates a {@link Session} by evaluating the HTTP response header. @@ -120,7 +119,7 @@ public interface Connection extends AutoCloseable { * {@link AcmeServerException} will be thrown. Otherwise a generic * {@link AcmeException} is thrown. */ - void throwAcmeException() throws AcmeException, IOException; + void throwAcmeException() throws AcmeException; /** * Closes the {@link Connection}, releasing all resources. diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java index dd0daaad..c4737ff1 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java @@ -44,6 +44,7 @@ import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeAgreementRequiredException; import org.shredzone.acme4j.exception.AcmeConflictException; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRateLimitExceededException; import org.shredzone.acme4j.exception.AcmeServerException; @@ -78,7 +79,7 @@ public class DefaultConnection implements Connection { } @Override - public int sendRequest(URI uri, Session session) throws IOException { + public int sendRequest(URI uri, Session session) throws AcmeException { if (uri == null) { throw new NullPointerException("uri must not be null"); } @@ -89,21 +90,25 @@ public class DefaultConnection implements Connection { LOG.debug("GET {}", uri); - conn = httpConnector.openConnection(uri); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag()); - conn.setDoOutput(false); + try { + conn = httpConnector.openConnection(uri); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Accept-Language", session.getLocale().toLanguageTag()); + conn.setDoOutput(false); - conn.connect(); + conn.connect(); - logHeaders(); + logHeaders(); - return conn.getResponseCode(); + return conn.getResponseCode(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); + } } @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws IOException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) throws AcmeException { if (uri == null) { throw new NullPointerException("uri must not be null"); } @@ -165,13 +170,15 @@ public class DefaultConnection implements Connection { updateSession(session); return conn.getResponseCode(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } catch (JoseException ex) { throw new AcmeProtocolException("Failed to generate a JSON request", ex); } } @Override - public Map readJsonResponse() throws IOException { + public Map readJsonResponse() throws AcmeException { assertConnectionIsOpen(); String contentType = conn.getHeaderField("Content-Type"); @@ -190,6 +197,8 @@ public class DefaultConnection implements Connection { result = JsonUtil.parseJson(response); LOG.debug("Result JSON: {}", response); } + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } catch (JoseException ex) { throw new AcmeProtocolException("Failed to parse response: " + response, ex); } @@ -213,7 +222,7 @@ public class DefaultConnection implements Connection { } @Override - public X509Certificate readCertificate() throws IOException { + public X509Certificate readCertificate() throws AcmeException { assertConnectionIsOpen(); String contentType = conn.getHeaderField("Content-Type"); @@ -224,6 +233,8 @@ public class DefaultConnection implements Connection { try (InputStream in = conn.getInputStream()) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(in); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } catch (CertificateException ex) { throw new AcmeProtocolException("Failed to read certificate", ex); } @@ -323,49 +334,49 @@ public class DefaultConnection implements Connection { } @Override - public void throwAcmeException() throws AcmeException, IOException { + public void throwAcmeException() throws AcmeException { assertConnectionIsOpen(); - if ("application/problem+json".equals(conn.getHeaderField("Content-Type"))) { - Map map = readJsonResponse(); - String type = (String) map.get("type"); - String detail = (String) map.get("detail"); + if (!"application/problem+json".equals(conn.getHeaderField("Content-Type"))) { + throw new AcmeException("HTTP " + getResponseCode() + ": " + getResponseMessage()); + } - if (detail == null) { - detail = "general problem"; - } + Map map = readJsonResponse(); - if (conn.getResponseCode() == HttpURLConnection.HTTP_CONFLICT) { - throw new AcmeConflictException(detail, getLocation()); - } + String type = (String) map.get("type"); + String detail = (String) map.get("detail"); - if (type == null) { - throw new AcmeException(detail); - } + if (detail == null) { + detail = "general problem"; + } - switch (type) { - case ACME_ERROR_PREFIX + "unauthorized": - case ACME_ERROR_PREFIX_DEPRECATED + "unauthorized": - throw new AcmeUnauthorizedException(type, detail); + if (getResponseCode() == HttpURLConnection.HTTP_CONFLICT) { + throw new AcmeConflictException(detail, getLocation()); + } - case ACME_ERROR_PREFIX + "agreementRequired": - case ACME_ERROR_PREFIX_DEPRECATED + "agreementRequired": - String instance = (String) map.get("instance"); - throw new AcmeAgreementRequiredException( - type, detail, getLink("terms-of-service"), - (instance != null ? resolveRelative(instance) : null)); + if (type == null) { + throw new AcmeException(detail); + } - case ACME_ERROR_PREFIX + "rateLimited": - case ACME_ERROR_PREFIX_DEPRECATED + "rateLimited": - throw new AcmeRateLimitExceededException( - type, detail, getRetryAfterHeader(), getLinks("rate-limit")); + switch (type) { + case ACME_ERROR_PREFIX + "unauthorized": + case ACME_ERROR_PREFIX_DEPRECATED + "unauthorized": + throw new AcmeUnauthorizedException(type, detail); - default: - throw new AcmeServerException(type, detail); - } - } else { - throw new AcmeException("HTTP " + conn.getResponseCode() + ": " - + conn.getResponseMessage()); + case ACME_ERROR_PREFIX + "agreementRequired": + case ACME_ERROR_PREFIX_DEPRECATED + "agreementRequired": + String instance = (String) map.get("instance"); + throw new AcmeAgreementRequiredException( + type, detail, getLink("terms-of-service"), + instance != null ? resolveRelative(instance) : null); + + case ACME_ERROR_PREFIX + "rateLimited": + case ACME_ERROR_PREFIX_DEPRECATED + "rateLimited": + throw new AcmeRateLimitExceededException( + type, detail, getRetryAfterHeader(), getLinks("rate-limit")); + + default: + throw new AcmeServerException(type, detail); } } @@ -392,6 +403,28 @@ public class DefaultConnection implements Connection { } } + /** + * Returns the last response code. + */ + private int getResponseCode() throws AcmeException { + try { + return conn.getResponseCode(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); + } + } + + /** + * Returns the last response message. + */ + private String getResponseMessage() throws AcmeException { + try { + return conn.getResponseMessage(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); + } + } + /** * Log all HTTP headers in debug mode. */ diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java index 21a51fbe..b1c33ef5 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j.connector; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -27,7 +26,6 @@ import java.util.NoSuchElementException; import org.shredzone.acme4j.AcmeResource; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; /** @@ -144,7 +142,6 @@ public abstract class ResourceIterator implements Iterat * Reads the next batch of URIs from the server, and fills the queue with the URIs. If * there is a "next" header, it is used for the next batch of URIs. */ - @SuppressWarnings("unchecked") private void readAndQueue() throws AcmeException { try (Connection conn = session.provider().connect()) { int rc = conn.sendRequest(nextUri, session); @@ -153,22 +150,33 @@ public abstract class ResourceIterator implements Iterat } Map json = conn.readJsonResponse(); - try { - Collection array = (Collection) json.get(field); - if (array != null) { - for (String uri : array) { - uriList.add(new URI(uri)); - } - } - } catch (ClassCastException ex) { - throw new AcmeProtocolException("Expected an array"); - } catch (URISyntaxException ex) { - throw new AcmeProtocolException("Invalid URI", ex); - } + fillUriList(json); nextUri = conn.getLink("next"); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); + } + } + + /** + * Fills the uri list with the URIs found in the desired field. + * + * @param json + * JSON map to read from + */ + private void fillUriList(Map json) { + try { + @SuppressWarnings("unchecked") + Collection array = (Collection) json.get(field); + if (array == null) { + return; + } + + for (String uri : array) { + uriList.add(new URI(uri)); + } + } catch (ClassCastException ex) { + throw new AcmeProtocolException("Expected an array", ex); + } catch (URISyntaxException ex) { + throw new AcmeProtocolException("Invalid URI", ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java index 2661eed2..55e764b7 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j.provider; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.util.Map; @@ -28,7 +27,6 @@ import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.DefaultConnection; import org.shredzone.acme4j.connector.HttpConnector; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeNetworkException; /** * Abstract implementation of {@link AcmeProvider}. It consists of a challenge @@ -56,8 +54,6 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { conn.updateSession(session); return conn.readJsonResponse(); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java index ef35f3d8..fd4f2cda 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java @@ -41,6 +41,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -358,7 +359,7 @@ public class DefaultConnectionTest { assertThat(ex.getType(), is("urn:zombie:error:apocalypse")); assertThat(ex.getMessage(), is("Zombie apocalypse in progress")); assertThat(ex.getAcmeErrorType(), is(nullValue())); - } catch (AcmeException | IOException ex) { + } catch (AcmeException ex) { fail("Expected an AcmeServerException"); } @@ -386,10 +387,10 @@ public class DefaultConnectionTest { conn.conn = mockUrlConnection; conn.throwAcmeException(); fail("Expected to fail"); + } catch (AcmeNetworkException ex) { + fail("Did not expect an AcmeNetworkException"); } catch (AcmeException ex) { assertThat(ex.getMessage(), not(isEmptyOrNullString())); - } catch (IOException ex) { - fail("Expected an AcmeException"); } verify(mockUrlConnection).getHeaderField("Content-Type");