/*
* Copyright 2008-2014 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and
* limitations under the License.
*/
package com.amazonaws.eclipse.core.diagnostic.utils;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.osgi.framework.Bundle;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.eclipse.core.diagnostic.model.ErrorReportDataModel;
import com.amazonaws.eclipse.core.diagnostic.model.PlatformEnvironmentDataModel;
import com.amazonaws.util.SdkHttpUtils;
/**
* A util class responsible for sending error report data to the Amazon
* HTMLForms system.
*/
public class AwsPortalFeedbackFormUtils {
private static final String BUG_REPORT_FORM_NAME = "aws-eclipse-error-report";
private static final String FORM_URL = String.format("https://aws.amazon.com/forms/%s", BUG_REPORT_FORM_NAME);
/** Used for extracting authenticity_token from the form HTML page */
private static final String AWS_FORM_INPUT_SELECTOR = "form.aws-form input";
private static final String AUTHENTICITY_TOKEN = "authenticity_token";
/**
* Each form field will be rendered as an HTML form input element with a
* name of "HTMLFormsForm-{fieldName}". We need to use the same field name
* for the POST request.
*/
private static final String FORM_FIELD_PREFIX = "HTMLFormsForm-";
/** All the fields supported by the form */
private static final String EMAIL = "email";
private static final String USER_DESCRIPTION = "user-description";
private static final String ERROR_STACKTRACE = "error-stacktrace";
private static final String ERROR_STATUS_MESSAGE = "error-status-message";
private static final String ECLIPSE_PLATFORM_ENV = "eclipse-platform-env";
private static final String INSTALLED_PLUGINS = "installed-plugins";
/**
* Send the error report data to the Amazon HTMLForms system.
*
* @throws AmazonClientException
* Thrown if the toolkit failed to retrieve a fresh
* authenticity_token, or if any client-side error occurred when
* sending the POST request and reading the POST response.
* @throws AmazonServiceException
* Thrown if the POST request was rejected with non-2xx status
* code.
*/
public static void reportBugToAws(final ErrorReportDataModel reportData)
throws AmazonClientException, AmazonServiceException {
/*
* We need to reuse the same http-client instance for both GET and POST
* operations. By doing this, we can rely on the http-client library to
* automatically handle session cookies that are required by the Brew system
* (e.g. '_awshome-brew_session' is returned via Set-Cookie header in the
* GET response, and we have to include the same cookie value in the next
* POST request.)
*/
final DefaultHttpClient formHttpClient = new DefaultHttpClient();
String token = getFreshAuthenticityToken(formHttpClient);
String payload = generateFormPostContent(reportData, token);
sendFormPostRequest(formHttpClient, payload);
}
/**
* Returns the value of 'authenticity_token' by requesting the form page
* from 'aws.amazon.com/forms'.
*
* @param httpClient
* The http-client instance to use when sending the GET request.
*/
private static String getFreshAuthenticityToken(final HttpClient httpClient) {
Document freshForm;
try {
HttpGet getForm = new HttpGet(FORM_URL);
HttpResponse response = httpClient.execute(getForm);
String formPageContent = IOUtils.toString(response.getEntity().getContent());
freshForm = Jsoup.parse(formPageContent);
} catch (IOException ioe) {
throw new AmazonClientException("Cannot get the form page from "
+ FORM_URL, ioe);
}
for (Element formInput : freshForm.select(AWS_FORM_INPUT_SELECTOR)) {
if (formInput.attr("name").equals(AUTHENTICITY_TOKEN)) {
return formInput.attr("value");
}
}
throw new AmazonClientException("Failed to extract " + AUTHENTICITY_TOKEN
+ " from " + FORM_URL);
}
/**
* Serialize the specified error report data into "x-www-form-urlencoded"
* format. This method also appends additional parameters that are
* implicitly required by the Amazon HTMLForms system (including
* authenticity_token).
*/
private static String generateFormPostContent(
final ErrorReportDataModel reportData,
final String authentityToken) {
StringBuilder content = new StringBuilder();
// These are the additional fields required by the POST API
content.append(AUTHENTICITY_TOKEN).append("=")
.append(SdkHttpUtils.urlEncode(authentityToken, false));
content.append("&_method=put");
// These are the "real" data fields
/* ============= User email ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(EMAIL).append("=");
content.append(SdkHttpUtils.urlEncode(reportData.getUserEmail(), false));
/* ============= User description of the error ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(USER_DESCRIPTION).append("=");
content.append(SdkHttpUtils.urlEncode(reportData.getUserDescription(), false));
/* ============= Error stack trace ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(ERROR_STACKTRACE).append("=");
content.append(SdkHttpUtils.urlEncode(
getStackTraceFromThrowable(reportData.getBug()), false));
/* ============= Error status message ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(ERROR_STATUS_MESSAGE).append("=");
content.append(SdkHttpUtils.urlEncode(reportData.getStatusMessage(), false));
PlatformEnvironmentDataModel env = reportData.getPlatformEnv();
/* ============= Platform environment ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(ECLIPSE_PLATFORM_ENV).append("=");
if (env != null) {
StringWriter eclipsePlatformEnv = new StringWriter();
PrintWriter pw = new PrintWriter(eclipsePlatformEnv);
pw.print("Eclipse platform version : ");
pw.println(env.getEclipsePlatformVersion());
pw.print("OS name : ");
pw.println(env.getOsName());
pw.print("OS version : ");
pw.println(env.getOsVersion());
pw.print("OS architecture : ");
pw.println(env.getOsArch());
pw.print("JVM name : ");
pw.println(env.getJavaVmName());
pw.print("JVM version : ");
pw.println(env.getJavaVmVersion());
pw.print("Java lang version : ");
pw.println(env.getJavaVersion());
pw.println();
pw.println();
content.append(SdkHttpUtils.urlEncode(eclipsePlatformEnv.toString(), false));
}
/* ============= Installed Plug-ins ============= */
content.append("&");
content.append(FORM_FIELD_PREFIX).append(INSTALLED_PLUGINS).append("=");
if (env != null) {
StringWriter installedPlugins = new StringWriter();
PrintWriter pw = new PrintWriter(installedPlugins);
for (Bundle bundle : env.getInstalledBundles()) {
pw.println(bundle.toString());
}
content.append(SdkHttpUtils.urlEncode(installedPlugins.toString(), false));
}
return content.toString();
}
/**
* Send the specified content by a POST request.
*
* @param httpClient
* The http-client instance to use when sending the POST request.
* @param payload
* The payload of the POST request.
*/
private static void sendFormPostRequest(final HttpClient httpClient,
final String payload) throws AmazonClientException, AmazonServiceException {
HttpPost request = new HttpPost(FORM_URL);
request.addHeader("Content-Type", "application/x-www-form-urlencoded");
try {
request.setEntity(new StringEntity(payload, "UTF-8"));
} catch (Exception e) {
throw new AmazonClientException("Unable to send POST request to " + FORM_URL, e);
}
HttpResponse response;
try {
response = httpClient.execute(request);
} catch (ClientProtocolException e) {
throw new AmazonClientException("Unable to send POST request to "
+ FORM_URL, e);
} catch (IOException e) {
throw new AmazonClientException("Unable to send POST request to "
+ FORM_URL, e);
}
if (response.getStatusLine().getStatusCode() / 100 != HttpStatus.SC_OK / 100) {
String responseContent;
try {
responseContent = IOUtils.toString(response.getEntity().getContent());
} catch (IllegalStateException e) {
throw new AmazonClientException(
"Unable to read POST response content.", e);
} catch (IOException e) {
throw new AmazonClientException(
"Unable to read POST response content.", e);
}
AmazonServiceException ase = new AmazonServiceException(
"Unable to send POST request to " + FORM_URL + " : "
+ responseContent);
ase.setStatusCode(response.getStatusLine().getStatusCode());
throw ase;
}
}
public static String getStackTraceFromThrowable(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.println();
if (t.getCause() != null) {
t.getCause().printStackTrace(pw);
}
return sw.toString();
}
}