diff --git a/app/src/main/java/at/bitfire/davdroid/journalmanager/BaseManager.java b/app/src/main/java/at/bitfire/davdroid/journalmanager/BaseManager.java index eef20483..ac859a87 100644 --- a/app/src/main/java/at/bitfire/davdroid/journalmanager/BaseManager.java +++ b/app/src/main/java/at/bitfire/davdroid/journalmanager/BaseManager.java @@ -4,6 +4,7 @@ import org.apache.commons.codec.Charsets; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.net.HttpURLConnection; import java.util.logging.Level; import at.bitfire.davdroid.App; @@ -36,10 +37,10 @@ abstract class BaseManager { if (!response.isSuccessful()) { switch (response.code()) { - case 401: + case HttpURLConnection.HTTP_UNAUTHORIZED: throw new Exceptions.UnauthorizedException("Failed to connect"); default: - throw new Exceptions.HttpException("Error getting"); + throw new Exceptions.HttpException(response); } } diff --git a/app/src/main/java/at/bitfire/davdroid/journalmanager/Exceptions.java b/app/src/main/java/at/bitfire/davdroid/journalmanager/Exceptions.java index 4d9deff4..8171bf43 100644 --- a/app/src/main/java/at/bitfire/davdroid/journalmanager/Exceptions.java +++ b/app/src/main/java/at/bitfire/davdroid/journalmanager/Exceptions.java @@ -1,5 +1,16 @@ package at.bitfire.davdroid.journalmanager; +import java.io.IOException; +import java.io.Serializable; +import java.net.HttpURLConnection; + +import at.bitfire.cert4android.Constants; +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; + public class Exceptions { public static class UnauthorizedException extends HttpException { public UnauthorizedException(String message) { @@ -13,19 +24,103 @@ public class Exceptions { } } - public static class HttpException extends Exception { + public static class IntegrityException extends Exception { + public IntegrityException(String message) { + super(message); + } + } + + public static class HttpException extends Exception implements Serializable { + + public final int status; + public final String message; + + public final String request, response; + public HttpException(String message) { super(message); + this.message = message; + + this.status = -1; + this.request = this.response = null; } public HttpException(int status, String message) { super(status + " " + message); + this.status = status; + this.message = message; + + request = response = null; } - } - public static class IntegrityException extends Exception { - public IntegrityException(String message) { - super(message); + public HttpException(Response response) { + super(response.code() + " " + response.message()); + + status = response.code(); + message = response.message(); + + /* As we don't know the media type and character set of request and response body, + only printable ASCII characters will be shown in clear text. Other octets will + be shown as "[xx]" where xx is the hex value of the octet. + */ + + // format request + Request request = response.request(); + StringBuilder formatted = new StringBuilder(); + formatted.append(request.method()).append(" ").append(request.url().encodedPath()).append("\n"); + Headers headers = request.headers(); + for (String name : headers.names()) { + for (String value : headers.values(name)) { + /* Redact authorization token. */ + if (name.equals("Authorization")) { + formatted.append(name).append(": ").append("XXXXXX").append("\n"); + } else { + formatted.append(name).append(": ").append(value).append("\n"); + } + } + } + if (request.body() != null) + try { + formatted.append("\n"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + while (!buffer.exhausted()) + appendByte(formatted, buffer.readByte()); + } catch (IOException e) { + Constants.log.warning("Couldn't read request body"); + } + this.request = formatted.toString(); + + // format response + formatted = new StringBuilder(); + formatted.append(response.protocol()).append(" ").append(response.code()).append(" ").append(response.message()).append("\n"); + headers = response.headers(); + for (String name : headers.names()) + for (String value : headers.values(name)) + formatted.append(name).append(": ").append(value).append("\n"); + if (response.body() != null) { + ResponseBody body = response.body(); + try { + formatted.append("\n"); + for (byte b : body.bytes()) + appendByte(formatted, b); + } catch (IOException e) { + Constants.log.warning("Couldn't read response body"); + } + body.close(); + } + this.response = formatted.toString(); + } + + private static void appendByte(StringBuilder formatted, byte b) { + if (b == '\r') + formatted.append("[CR]"); + else if (b == '\n') + formatted.append("[LF]\n"); + else if (b >= 0x20 && b <= 0x7E) // printable ASCII + formatted.append((char) b); + else + formatted.append("[" + Integer.toHexString((int) b & 0xff) + "]"); } } } diff --git a/app/src/main/java/at/bitfire/davdroid/journalmanager/JournalAuthenticator.java b/app/src/main/java/at/bitfire/davdroid/journalmanager/JournalAuthenticator.java index 6e1ae7a7..baafd298 100644 --- a/app/src/main/java/at/bitfire/davdroid/journalmanager/JournalAuthenticator.java +++ b/app/src/main/java/at/bitfire/davdroid/journalmanager/JournalAuthenticator.java @@ -1,6 +1,7 @@ package at.bitfire.davdroid.journalmanager; import java.io.IOException; +import java.net.HttpURLConnection; import java.util.logging.Level; import at.bitfire.davdroid.App; @@ -44,10 +45,10 @@ public class JournalAuthenticator { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return GsonHelper.gson.fromJson(response.body().charStream(), AuthResponse.class).token; - } else if (response.code() == 400) { + } else if (response.code() == HttpURLConnection.HTTP_BAD_REQUEST) { throw new Exceptions.UnauthorizedException("Username or password incorrect"); } else { - throw new Exceptions.HttpException("Error authenticating"); + throw new Exceptions.HttpException(response); } } catch (IOException e) { App.log.log(Level.SEVERE, "Couldn't download external resource", e); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java index 1f14a314..ad4e7e31 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java @@ -181,13 +181,11 @@ public class DebugInfoActivity extends AppCompatActivity implements LoaderManage report.append("Authority: ").append(authority).append("\n"); if (throwable instanceof HttpException) { - /* FIXME HttpException http = (HttpException)throwable; if (http.request != null) report.append("\nHTTP REQUEST:\n").append(http.request).append("\n\n"); if (http.response != null) report.append("HTTP RESPONSE:\n").append(http.response).append("\n"); - */ } if (throwable != null)