Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix nullpointer in HttpSender & Exception in ReportConverter #542

Merged
merged 4 commits into from
Feb 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/main/java/org/acra/legacy/ReportConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,21 @@ void convert() {
CrashReportData data = legacyLoad(new InputStreamReader(in, "ISO8859-1")); //$NON-NLS-1$
if (data.containsKey(ReportField.REPORT_ID) && data.containsKey(ReportField.USER_CRASH_DATE)) {
persister.store(data, report);
converted++;
} else {
//reports without these keys are probably invalid
IOUtils.deleteReport(report);
}
} catch (IOException e) {
ACRA.log.w(LOG_TAG, "Unable to read report file " + report.getPath(), e);
} catch (Throwable e) {
try {
//If this succeeds the report has already been converted, happens e.g. on preference clear.
persister.load(report);
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Tried to convert already converted report file " + report.getPath() + ". Ignoring");
} catch (Throwable t) {
//File matches neither of the known formats, remove it.
ACRA.log.w(LOG_TAG, "Unable to read report file " + report.getPath() + ". Deleting", e);
IOUtils.deleteReport(report);
}
} finally {
IOUtils.safeClose(in);
}
Expand Down
221 changes: 131 additions & 90 deletions src/main/java/org/acra/sender/HttpSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import org.acra.config.ACRAConfiguration;
import org.acra.model.Element;
import org.acra.util.HttpRequest;
import org.json.JSONObject;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -48,17 +50,14 @@
* {@link ReportField} enum values) or based on your own conversion Map from
* {@link ReportField} values to String.
* </p>
*
* <p>
* To use specific POST parameter names, you can provide your own report fields
* mapping scheme:
* </p>
*
* <pre>
* Just create and declare a {@link ReportSenderFactory} that constructs a mapping
* from each {@link ReportField} to another name.
* </pre>
*
*/
public class HttpSender implements ReportSender {

Expand All @@ -67,7 +66,20 @@ public class HttpSender implements ReportSender {
* supported.
*/
public enum Method {
POST, PUT
POST {
@Override
URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException {
return new URL(baseUrl);
}
},
PUT {
@Override
URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException {
return new URL(baseUrl + '/' + report.getProperty(ReportField.REPORT_ID));
}
};

abstract URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException;
}

/**
Expand All @@ -77,28 +89,36 @@ public enum Method {
public enum Type {
/**
* Send data as a www form encoded list of key/values.
*
* @see <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4">Form content types</a>
*/
FORM {
@NonNull
FORM("application/x-www-form-urlencoded") {
@Override
public String getContentType() {
return "application/x-www-form-urlencoded";
String convertReport(HttpSender sender, CrashReportData report) throws IOException {
return HttpRequest.getParamsAsFormString(sender.convertToForm(report));
}
},
/**
* Send data as a structured JSON tree.
*/
JSON {
@NonNull
JSON("application/json") {
@Override
public String getContentType() {
return "application/json";
String convertReport(HttpSender sender, CrashReportData report) throws IOException {
return sender.convertToJson(report).toString();
}
};
private final String contentType;

Type(String contentType) {
this.contentType = contentType;
}

@NonNull
public abstract String getContentType();
public String getContentType() {
return contentType;
}

abstract String convertReport(HttpSender sender, CrashReportData report) throws IOException;
}

private final ACRAConfiguration config;
Expand All @@ -112,29 +132,43 @@ public String getContentType() {
@Nullable
private String mPassword;

/**
* <p>
* Create a new HttpSender instance with its destination taken from the supplied config.
* Uses {@link ReportField} values converted to String with .toString() as form parameters.
* </p>
*
* @param config AcraConfig declaring the
* @param method HTTP {@link Method} to be used to send data. Currently only
* {@link Method#POST} and {@link Method#PUT} are available. If
* {@link Method#PUT} is used, the {@link ReportField#REPORT_ID}
* is appended to the formUri to be compliant with RESTful APIs.
* @param type {@link Type} of encoding used to send the report body.
* {@link Type#FORM} is a simple Key/Value pairs list as defined
* by the application/x-www-form-urlencoded mime type.
*/
public HttpSender(@NonNull ACRAConfiguration config, @NonNull Method method, @NonNull Type type) {
this(config, method, type, null);
}

/**
* <p>
* Create a new HttpSender instance with its destination taken from the supplied config.
* </p>
*
* @param config AcraConfig declaring the
* @param method
* HTTP {@link Method} to be used to send data. Currently only
* {@link Method#POST} and {@link Method#PUT} are available. If
* {@link Method#PUT} is used, the {@link ReportField#REPORT_ID}
* is appended to the formUri to be compliant with RESTful APIs.
*
* @param type
* {@link Type} of encoding used to send the report body.
* {@link Type#FORM} is a simple Key/Value pairs list as defined
* by the application/x-www-form-urlencoded mime type.
*
* @param mapping
* Applies only to {@link Method#POST} method parameter. If null,
* POST parameters will be named with {@link ReportField} values
* converted to String with .toString(). If not null, POST
* parameters will be named with the result of
* mapping.get(ReportField.SOME_FIELD);
* @param config AcraConfig declaring the
* @param method HTTP {@link Method} to be used to send data. Currently only
* {@link Method#POST} and {@link Method#PUT} are available. If
* {@link Method#PUT} is used, the {@link ReportField#REPORT_ID}
* is appended to the formUri to be compliant with RESTful APIs.
* @param type {@link Type} of encoding used to send the report body.
* {@link Type#FORM} is a simple Key/Value pairs list as defined
* by the application/x-www-form-urlencoded mime type.
* @param mapping Applies only to {@link Method#POST} method parameter. If null,
* POST parameters will be named with {@link ReportField} values
* converted to String with .toString(). If not null, POST
* parameters will be named with the result of
* mapping.get(ReportField.SOME_FIELD);
*/
public HttpSender(@NonNull ACRAConfiguration config, @NonNull Method method, @NonNull Type type, @Nullable Map<ReportField, String> mapping) {
this(config, method, type, null, mapping);
Expand All @@ -146,25 +180,20 @@ public HttpSender(@NonNull ACRAConfiguration config, @NonNull Method method, @No
* a parameter. Configuration changes to the formUri are not applied.
* </p>
*
* @param config AcraConfig declaring the
* @param method
* HTTP {@link Method} to be used to send data. Currently only
* {@link Method#POST} and {@link Method#PUT} are available. If
* {@link Method#PUT} is used, the {@link ReportField#REPORT_ID}
* is appended to the formUri to be compliant with RESTful APIs.
*
* @param type
* {@link Type} of encoding used to send the report body.
* {@link Type#FORM} is a simple Key/Value pairs list as defined
* by the application/x-www-form-urlencoded mime type.
* @param formUri
* The URL of your server-side crash report collection script.
* @param mapping
* Applies only to {@link Method#POST} method parameter. If null,
* POST parameters will be named with {@link ReportField} values
* converted to String with .toString(). If not null, POST
* parameters will be named with the result of
* mapping.get(ReportField.SOME_FIELD);
* @param config AcraConfig declaring the
* @param method HTTP {@link Method} to be used to send data. Currently only
* {@link Method#POST} and {@link Method#PUT} are available. If
* {@link Method#PUT} is used, the {@link ReportField#REPORT_ID}
* is appended to the formUri to be compliant with RESTful APIs.
* @param type {@link Type} of encoding used to send the report body.
* {@link Type#FORM} is a simple Key/Value pairs list as defined
* by the application/x-www-form-urlencoded mime type.
* @param formUri The URL of your server-side crash report collection script.
* @param mapping Applies only to {@link Method#POST} method parameter. If null,
* POST parameters will be named with {@link ReportField} values
* converted to String with .toString(). If not null, POST
* parameters will be named with the result of
* mapping.get(ReportField.SOME_FIELD);
*/
public HttpSender(@NonNull ACRAConfiguration config, @NonNull Method method, @NonNull Type type, @Nullable String formUri, @Nullable Map<ReportField, String> mapping) {
this.config = config;
Expand All @@ -173,66 +202,39 @@ public HttpSender(@NonNull ACRAConfiguration config, @NonNull Method method, @No
mMapping = mapping;
mType = type;
mUsername = null;
mPassword = null;
mPassword = null;
}

/**
* <p>
* Set credentials for this HttpSender that override (if present) the ones
* set globally.
* </p>
*
* @param username
* The username to set for HTTP Basic Auth.
* @param password
* The password to set for HTTP Basic Auth.
*
* @param username The username to set for HTTP Basic Auth.
* @param password The password to set for HTTP Basic Auth.
*/
@SuppressWarnings( "unused" )
@SuppressWarnings("unused")
public void setBasicAuth(@Nullable String username, @Nullable String password) {
mUsername = username;
mPassword = password;
}
}

@Override
public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException {

try {
URL reportUrl = mFormUri == null ? new URL(config.formUri()) : new URL(mFormUri.toString());
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Connect to " + reportUrl.toString());

final String login = mUsername != null ? mUsername : isNull(config.formUriBasicAuthLogin()) ? null : config.formUriBasicAuthLogin();
final String password = mPassword != null ? mPassword : isNull(config.formUriBasicAuthPassword()) ? null : config.formUriBasicAuthPassword();
String baseUrl = mFormUri == null ? config.formUri() : mFormUri.toString();
if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Connect to " + baseUrl);

final HttpRequest request = new HttpRequest(config);
request.setConnectionTimeOut(config.connectionTimeout());
request.setSocketTimeOut(config.socketTimeout());
request.setLogin(login);
request.setPassword(password);
request.setHeaders(config.getHttpHeaders());
configureHttpRequest(request);

// Generate report body depending on requested type
final String reportAsString;
switch (mType) {
case JSON:
reportAsString = report.toJSON().toString();
break;
case FORM:
default:
final Map<String, String> finalReport = remap(report);
reportAsString = HttpRequest.getParamsAsFormString(finalReport);
break;
}
final String reportAsString = mType.convertReport(this, report);

// Adjust URL depending on method
switch (mMethod) {
case POST:
break;
case PUT:
reportUrl = new URL(reportUrl.toString() + '/' + report.getProperty(ReportField.REPORT_ID));
break;
default:
throw new UnsupportedOperationException("Unknown method: " + mMethod.name());
}
URL reportUrl = mMethod.createURL(baseUrl, report);
request.send(context, reportUrl, mMethod, reportAsString, mType);

} catch (@NonNull IOException e) {
Expand All @@ -241,6 +243,44 @@ public void send(@NonNull Context context, @NonNull CrashReportData report) thro
}
}

/**
* Configure the HttpRequest. Subclasses can perform additional configuration here
*
* @param request the request to configure
*/
@SuppressWarnings({"WeakerAccess"})
protected void configureHttpRequest(HttpRequest request) {
final String login = mUsername != null ? mUsername : isNull(config.formUriBasicAuthLogin()) ? null : config.formUriBasicAuthLogin();
final String password = mPassword != null ? mPassword : isNull(config.formUriBasicAuthPassword()) ? null : config.formUriBasicAuthPassword();
request.setConnectionTimeOut(config.connectionTimeout());
request.setSocketTimeOut(config.socketTimeout());
request.setLogin(login);
request.setPassword(password);
request.setHeaders(config.getHttpHeaders());
}

/**
* Convert a report to json
*
* @param report the report to convert
* @return a json representation of the report
*/
@SuppressWarnings("WeakerAccess")
protected JSONObject convertToJson(CrashReportData report) {
return report.toJSON();
}

/**
* Convert a report to a form-prepared map
*
* @param report the report to convert
* @return a form representation of the report
*/
@SuppressWarnings("WeakerAccess")
protected Map<String, String> convertToForm(CrashReportData report) {
return remap(report);
}

@NonNull
private Map<String, String> remap(@NonNull Map<ReportField, Element> report) {

Expand All @@ -251,7 +291,8 @@ private Map<String, String> remap(@NonNull Map<ReportField, Element> report) {

final Map<String, String> finalReport = new HashMap<String, String>(report.size());
for (ReportField field : fields) {
String value = TextUtils.join("\n", report.get(field).flatten());
Element element = report.get(field);
String value = element != null ? TextUtils.join("\n", element.flatten()) : null;
if (mMapping == null || mMapping.get(field) == null) {
finalReport.put(field.toString(), value);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/acra/sender/HttpSenderFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public final class HttpSenderFactory implements ReportSenderFactory {
@NonNull
@Override
public ReportSender create(@NonNull Context context, @NonNull ACRAConfiguration config) {
return new HttpSender(config, config.httpMethod(), config.reportType(), null);
return new HttpSender(config, config.httpMethod(), config.reportType());
}
}