diff --git a/acra-core/src/main/java/org/acra/annotation/AcraCore.java b/acra-core/src/main/java/org/acra/annotation/AcraCore.java index 7f684b8ed..3d728c7b8 100644 --- a/acra-core/src/main/java/org/acra/annotation/AcraCore.java +++ b/acra-core/src/main/java/org/acra/annotation/AcraCore.java @@ -268,7 +268,7 @@ *

* Side effects: * diff --git a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java index a7e91bbd0..8124a1b73 100644 --- a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java @@ -174,7 +174,6 @@ protected void configureHeaders(@NonNull HttpURLConnection connection, @Nullable @SuppressWarnings("WeakerAccess") protected void writeContent(@NonNull HttpURLConnection connection, @NonNull Method method, @NonNull T content) throws IOException { - final byte[] contentAsBytes = asBytes(content); // write output - see http://developer.android.com/reference/java/net/HttpURLConnection.html connection.setRequestMethod(method.name()); connection.setDoOutput(true); @@ -187,15 +186,14 @@ protected void writeContent(@NonNull HttpURLConnection connection, @NonNull Meth final OutputStream outputStream = senderConfiguration.compress() ? new GZIPOutputStream(connection.getOutputStream(), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) : new BufferedOutputStream(connection.getOutputStream()); try { - outputStream.write(contentAsBytes); + write(outputStream, content); outputStream.flush(); } finally { IOUtils.safeClose(outputStream); } } - @NonNull - protected abstract byte[] asBytes(@NonNull T content) throws IOException; + protected abstract void write(OutputStream outputStream, @NonNull T content) throws IOException; @SuppressWarnings("WeakerAccess") protected void handleResponse(int responseCode, String responseMessage) throws IOException { diff --git a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java index 22700f9fe..8912b8158 100644 --- a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java @@ -26,6 +26,7 @@ import org.acra.util.UriUtils; import java.io.IOException; +import java.io.OutputStream; import java.util.Map; /** @@ -48,9 +49,8 @@ protected String getContentType(@NonNull Context context, @NonNull Uri uri) { return UriUtils.getMimeType(context, uri); } - @NonNull @Override - protected byte[] asBytes(@NonNull Uri content) throws IOException { - return UriUtils.uriToByteArray(context, content); + protected void write(OutputStream outputStream, @NonNull Uri content) throws IOException { + UriUtils.copyFromUri(context, outputStream, content); } } diff --git a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java index 8225983d8..246493325 100644 --- a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java @@ -24,6 +24,7 @@ import org.acra.sender.HttpSender; import java.io.IOException; +import java.io.OutputStream; import java.util.Map; /** @@ -46,9 +47,8 @@ protected String getContentType(@NonNull Context context, @NonNull String s) { return contentType; } - @NonNull @Override - protected byte[] asBytes(@NonNull String content) throws IOException { - return content.getBytes(ACRAConstants.UTF8); + protected void write(OutputStream outputStream, @NonNull String content) throws IOException { + outputStream.write(content.getBytes(ACRAConstants.UTF8)); } } diff --git a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java index d4b7b327d..4155ded9e 100644 --- a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java @@ -21,21 +21,18 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; - +import org.acra.ACRA; import org.acra.ACRAConstants; import org.acra.config.CoreConfiguration; import org.acra.sender.HttpSender; import org.acra.util.UriUtils; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.*; import java.util.List; import java.util.Map; /** - * Produces RFC 1341 compliant requests + * Produces RFC 7578 compliant requests * * @author F43nd1r * @since 11.03.2017 @@ -46,7 +43,10 @@ public class MultipartHttpRequest extends BaseHttpRequest private static final String BOUNDARY = "%&ACRA_REPORT_DIVIDER&%"; private static final String BOUNDARY_FIX = "--"; private static final String NEW_LINE = "\r\n"; - private static final String CONTENT_TYPE = "Content-Type: "; + private static final String SECTION_START = NEW_LINE + BOUNDARY_FIX + BOUNDARY + NEW_LINE; + private static final String MESSAGE_END = NEW_LINE + BOUNDARY_FIX + BOUNDARY + BOUNDARY_FIX + NEW_LINE; + private static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"" + NEW_LINE; + private static final String CONTENT_TYPE = "Content-Type: %s" + NEW_LINE; private final Context context; private final String contentType; @@ -60,31 +60,30 @@ public MultipartHttpRequest(@NonNull CoreConfiguration config, @NonNull Context @NonNull @Override protected String getContentType(@NonNull Context context, @NonNull Pair> stringListPair) { - return "multipart/mixed; boundary=" + BOUNDARY; + return "multipart/form-data; boundary=" + BOUNDARY; } - @NonNull @Override - protected byte[] asBytes(@NonNull Pair> content) throws IOException { - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final Writer writer = new OutputStreamWriter(outputStream, ACRAConstants.UTF8); - //noinspection TryFinallyCanBeTryWithResources we do not target api 19 - try { - writer.append(NEW_LINE).append(BOUNDARY_FIX).append(BOUNDARY).append(NEW_LINE); - writer.append(CONTENT_TYPE).append(contentType).append(NEW_LINE).append(NEW_LINE); - writer.append(content.first); - for (Uri uri : content.second) { - writer.append(NEW_LINE).append(BOUNDARY_FIX).append(BOUNDARY).append(NEW_LINE); - writer.append("Content-Disposition: attachment; filename=\"").append(UriUtils.getFileNameFromUri(context, uri)).append('"').append(NEW_LINE); - writer.append(CONTENT_TYPE).append(UriUtils.getMimeType(context, uri)).append(NEW_LINE).append(NEW_LINE); - writer.flush(); - outputStream.write(UriUtils.uriToByteArray(context, uri)); + protected void write(OutputStream outputStream, @NonNull Pair> content) throws IOException { + final PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, ACRAConstants.UTF8)); + writer.append(SECTION_START) + .format(CONTENT_DISPOSITION, "ACRA_REPORT", "") + .format(CONTENT_TYPE, contentType) + .append(NEW_LINE) + .append(content.first); + for (Uri uri : content.second) { + try { + String name = UriUtils.getFileNameFromUri(context, uri); + writer.append(SECTION_START) + .format(CONTENT_DISPOSITION, "ACRA_ATTACHMENT", name) + .format(CONTENT_TYPE, UriUtils.getMimeType(context, uri)) + .append(NEW_LINE) + .flush(); + UriUtils.copyFromUri(context, outputStream, uri); + } catch (FileNotFoundException e) { + ACRA.log.w("Not sending attachment", e); } - writer.append(NEW_LINE).append(BOUNDARY_FIX).append(BOUNDARY).append(BOUNDARY_FIX).append(NEW_LINE); - writer.flush(); - return outputStream.toByteArray(); - } finally { - writer.close(); } + writer.append(MESSAGE_END).flush(); } } diff --git a/acra-http/src/main/java/org/acra/sender/HttpSender.java b/acra-http/src/main/java/org/acra/sender/HttpSender.java index 335e671b6..c3f6e42d8 100644 --- a/acra-http/src/main/java/org/acra/sender/HttpSender.java +++ b/acra-http/src/main/java/org/acra/sender/HttpSender.java @@ -20,7 +20,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; - import org.acra.ACRA; import org.acra.ACRAConstants; import org.acra.ReportField; @@ -36,6 +35,7 @@ import org.acra.util.InstanceCreator; import org.acra.util.UriUtils; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -51,30 +51,6 @@ */ public class HttpSender implements ReportSender { - /** - * Available HTTP methods to send data. Only POST and PUT are currently - * supported. - */ - public enum Method { - POST { - @NonNull - @Override - URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { - return new URL(baseUrl); - } - }, - PUT { - @NonNull - @Override - URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { - return new URL(baseUrl + '/' + report.getString(ReportField.REPORT_ID)); - } - }; - - @NonNull - abstract URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException; - } - private final CoreConfiguration config; private final HttpSenderConfiguration httpConfig; private final Uri mFormUri; @@ -201,8 +177,12 @@ protected void postMultipart(@NonNull CoreConfiguration configuration, @NonNull protected void putAttachment(@NonNull CoreConfiguration configuration, @NonNull Context context, @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers, @NonNull URL url, @NonNull Uri attachment) throws IOException { - final URL attachmentUrl = new URL(url.toString() + "-" + UriUtils.getFileNameFromUri(context, attachment)); - new BinaryHttpRequest(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers).send(attachmentUrl, attachment); + try { + final URL attachmentUrl = new URL(url.toString() + "-" + UriUtils.getFileNameFromUri(context, attachment)); + new BinaryHttpRequest(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers).send(attachmentUrl, attachment); + } catch (FileNotFoundException e) { + ACRA.log.w("Not sending attachment", e); + } } /** @@ -223,4 +203,28 @@ private boolean isNull(@Nullable String aString) { return aString == null || ACRAConstants.NULL_VALUE.equals(aString); } + /** + * Available HTTP methods to send data. Only POST and PUT are currently + * supported. + */ + public enum Method { + POST { + @NonNull + @Override + URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { + return new URL(baseUrl); + } + }, + PUT { + @NonNull + @Override + URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { + return new URL(baseUrl + '/' + report.getString(ReportField.REPORT_ID)); + } + }; + + @NonNull + abstract URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException; + } + } \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/util/UriUtils.java b/acra-http/src/main/java/org/acra/util/UriUtils.java index 9d2b20b81..0fccda569 100644 --- a/acra-http/src/main/java/org/acra/util/UriUtils.java +++ b/acra-http/src/main/java/org/acra/util/UriUtils.java @@ -22,14 +22,13 @@ import android.net.Uri; import android.provider.OpenableColumns; import android.support.annotation.NonNull; - import org.acra.ACRAConstants; import org.acra.attachment.AcraContentProvider; -import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; /** * @author F43nd1r @@ -40,44 +39,27 @@ public final class UriUtils { private UriUtils() { } - @NonNull - public static byte[] uriToByteArray(@NonNull Context context, @NonNull Uri uri) throws IOException { - final InputStream inputStream = context.getContentResolver().openInputStream(uri); - if (inputStream == null) { - throw new FileNotFoundException("Could not open " + uri.toString()); - } - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final byte[] buffer = new byte[ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); + public static void copyFromUri(@NonNull Context context, @NonNull OutputStream outputStream, @NonNull Uri uri) throws IOException { + try (final InputStream inputStream = context.getContentResolver().openInputStream(uri)) { + if (inputStream == null) { + throw new FileNotFoundException("Could not open " + uri.toString()); + } + final byte[] buffer = new byte[ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } } - return outputStream.toByteArray(); } @NonNull - public static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri) { - String result = null; - if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { - final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - if (result == null) { - result = uri.getPath(); - final int cut = result.lastIndexOf('/'); - if (cut != -1) { - result = result.substring(cut + 1); + public static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { + try (Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } - return result; + throw new FileNotFoundException("Could not resolve filename of " + uri); } @NonNull