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:
*
- * - POST mode: requests will be sent with content-type multipart/mixed
+ * - POST mode: requests will be sent with content-type multipart/form-data
* - PUT mode: There will be additional requests with the attachments. Naming scheme: [report-id]-[filename]
* - EMAIL mode: Some email clients do not support attachments, so some email may lack these attachments. Note that attachments might be readable to email clients when they are sent.
*
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