/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.core.net.internal;
import static org.xmind.core.net.internal.EncodingUtils.toAsciiBytes;
import static org.xmind.core.net.internal.EncodingUtils.urlEncode;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmind.core.net.IDataStore;
import org.xmind.core.net.JSONStore;
import org.xmind.core.net.http.HttpRequest;
/**
* @deprecated Use {@link HttpRequest} instead
* @author Frank Shaka
*/
public class XMindNetRequest {
public static final int HTTP_PREPARING = 0;
public static final int HTTP_CONNECTING = 1;
public static final int HTTP_SENDING = 2;
public static final int HTTP_WAITING = 3;
public static final int HTTP_RECEIVING = 4;
public static final int HTTP_ERROR = 999;
/* 2XX: generally "OK" */
/**
* HTTP Status-Code 200: OK.
*/
public static final int HTTP_OK = HttpURLConnection.HTTP_OK;
/**
* HTTP Status-Code 201: Created.
*/
public static final int HTTP_CREATED = HttpURLConnection.HTTP_CREATED;
/**
* HTTP Status-Code 202: Accepted.
*/
public static final int HTTP_ACCEPTED = HttpURLConnection.HTTP_ACCEPTED;
/**
* HTTP Status-Code 203: Non-Authoritative Information.
*/
public static final int HTTP_NOT_AUTHORITATIVE = HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
/**
* HTTP Status-Code 204: No Content.
*/
public static final int HTTP_NO_CONTENT = HttpURLConnection.HTTP_NO_CONTENT;
/**
* HTTP Status-Code 205: Reset Content.
*/
public static final int HTTP_RESET = HttpURLConnection.HTTP_RESET;
/**
* HTTP Status-Code 206: Partial Content.
*/
public static final int HTTP_PARTIAL = HttpURLConnection.HTTP_PARTIAL;
/* 3XX: relocation/redirect */
/**
* HTTP Status-Code 300: Multiple Choices.
*/
public static final int HTTP_MULT_CHOICE = HttpURLConnection.HTTP_MULT_CHOICE;
/**
* HTTP Status-Code 301: Moved Permanently.
*/
public static final int HTTP_MOVED_PERM = HttpURLConnection.HTTP_MOVED_PERM;
/**
* HTTP Status-Code 302: Temporary Redirect.
*/
public static final int HTTP_MOVED_TEMP = HttpURLConnection.HTTP_MOVED_TEMP;
/**
* HTTP Status-Code 303: See Other.
*/
public static final int HTTP_SEE_OTHER = HttpURLConnection.HTTP_SEE_OTHER;
/**
* HTTP Status-Code 304: Not Modified.
*/
public static final int HTTP_NOT_MODIFIED = HttpURLConnection.HTTP_NOT_MODIFIED;
/**
* HTTP Status-Code 305: Use Proxy.
*/
public static final int HTTP_USE_PROXY = HttpURLConnection.HTTP_USE_PROXY;
/* 4XX: client error */
/**
* HTTP Status-Code 400: Bad Request.
*/
public static final int HTTP_BAD_REQUEST = HttpURLConnection.HTTP_BAD_REQUEST;
/**
* HTTP Status-Code 401: Unauthorized.
*/
public static final int HTTP_UNAUTHORIZED = HttpURLConnection.HTTP_UNAUTHORIZED;
/**
* HTTP Status-Code 402: Payment Required.
*/
public static final int HTTP_PAYMENT_REQUIRED = HttpURLConnection.HTTP_PAYMENT_REQUIRED;
/**
* HTTP Status-Code 403: Forbidden.
*/
public static final int HTTP_FORBIDDEN = HttpURLConnection.HTTP_FORBIDDEN;
/**
* HTTP Status-Code 404: Not Found.
*/
public static final int HTTP_NOT_FOUND = HttpURLConnection.HTTP_NOT_FOUND;
/**
* HTTP Status-Code 405: Method Not Allowed.
*/
public static final int HTTP_BAD_METHOD = HttpURLConnection.HTTP_BAD_METHOD;
/**
* HTTP Status-Code 406: Not Acceptable.
*/
public static final int HTTP_NOT_ACCEPTABLE = HttpURLConnection.HTTP_NOT_ACCEPTABLE;
/**
* HTTP Status-Code 407: Proxy Authentication Required.
*/
public static final int HTTP_PROXY_AUTH = HttpURLConnection.HTTP_PROXY_AUTH;
/**
* HTTP Status-Code 408: Request Time-Out.
*/
public static final int HTTP_CLIENT_TIMEOUT = HttpURLConnection.HTTP_CLIENT_TIMEOUT;
/**
* HTTP Status-Code 409: Conflict.
*/
public static final int HTTP_CONFLICT = HttpURLConnection.HTTP_CONFLICT;
/**
* HTTP Status-Code 410: Gone.
*/
public static final int HTTP_GONE = HttpURLConnection.HTTP_GONE;
/**
* HTTP Status-Code 411: Length Required.
*/
public static final int HTTP_LENGTH_REQUIRED = HttpURLConnection.HTTP_LENGTH_REQUIRED;
/**
* HTTP Status-Code 412: Precondition Failed.
*/
public static final int HTTP_PRECON_FAILED = HttpURLConnection.HTTP_PRECON_FAILED;
/**
* HTTP Status-Code 413: Request HttpEntity Too Large.
*/
public static final int HTTP_ENTITY_TOO_LARGE = HttpURLConnection.HTTP_ENTITY_TOO_LARGE;
/**
* HTTP Status-Code 414: Request-URI Too Large.
*/
public static final int HTTP_REQ_TOO_LONG = HttpURLConnection.HTTP_REQ_TOO_LONG;
/**
* HTTP Status-Code 415: Unsupported Media Type.
*/
public static final int HTTP_UNSUPPORTED_TYPE = HttpURLConnection.HTTP_UNSUPPORTED_TYPE;
/* 5XX: server error */
/**
* HTTP Status-Code 500: Internal Server Error.
*/
public static final int HTTP_INTERNAL_ERROR = HttpURLConnection.HTTP_INTERNAL_ERROR;
/**
* HTTP Status-Code 501: Not Implemented.
*/
public static final int HTTP_NOT_IMPLEMENTED = HttpURLConnection.HTTP_NOT_IMPLEMENTED;
/**
* HTTP Status-Code 502: Bad Gateway.
*/
public static final int HTTP_BAD_GATEWAY = HttpURLConnection.HTTP_BAD_GATEWAY;
/**
* HTTP Status-Code 503: Service Unavailable.
*/
public static final int HTTP_UNAVAILABLE = HttpURLConnection.HTTP_UNAVAILABLE;
/**
* HTTP Status-Code 504: Gateway Timeout.
*/
public static final int HTTP_GATEWAY_TIMEOUT = HttpURLConnection.HTTP_GATEWAY_TIMEOUT;
/**
* HTTP Status-Code 505: HTTP Version Not Supported.
*/
public static final int HTTP_VERSION = HttpURLConnection.HTTP_VERSION;
private static final boolean DEBUG_ALL = Activator
.isDebugging("/debug/http/requests"); //$NON-NLS-1$
private static final boolean DEBUG_TO_STDOUT = DEBUG_ALL
|| Activator.isDebugging("/debug/requests/stdout"); //$NON-NLS-1$
private static final boolean DEBUG_ASSC = Activator
.isDebugging("/debug/requests/assc"); //$NON-NLS-1$
private static final String DEFAULT_DOMAIN = "www.xmind.net"; //$NON-NLS-1$
private static final String HEAD = "HEAD"; //$NON-NLS-1$
private static final String GET = "GET"; //$NON-NLS-1$
private static final String POST = "POST"; //$NON-NLS-1$
private static final String PUT = "PUT"; //$NON-NLS-1$
private static final String DELETE = "DELETE"; //$NON-NLS-1$
private static Set<String> WRITABLE_METHODS = new HashSet<String>(
Arrays.asList(POST, PUT));
protected static class NamedValue {
public String name;
public Object value;
private String encodedName = null;
private String encodedValue = null;
public NamedValue(String name, Object value) {
this.name = name;
this.value = value;
}
public String getValue() {
return value == null ? "" : value.toString(); //$NON-NLS-1$
}
public String getEncodedName() {
if (encodedName == null) {
encodedName = urlEncode(name);
}
return encodedName;
}
public String getEncodedValue() {
if (encodedValue == null) {
encodedValue = urlEncode(value);
}
return encodedValue;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s: %s", name, value); //$NON-NLS-1$
}
}
protected static abstract class RequestWriter {
private List<NamedValue> parameters;
public void init(List<NamedValue> parameters) {
this.parameters = parameters;
}
/**
* @return the parameters
*/
protected List<NamedValue> getParameters() {
return parameters;
}
public abstract String getContentType();
public abstract long getContentLength();
public abstract void write(OutputStream stream) throws IOException;
}
protected static class FormSubmitter extends RequestWriter {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; //$NON-NLS-1$
private byte[] formData = null;
private byte[] getFormData() {
if (formData != null)
return formData;
formData = toAsciiBytes(toQueryString(getParameters()));
return formData;
}
public String getContentType() {
return FORM_CONTENT_TYPE;
}
public long getContentLength() {
return getFormData().length;
}
public void write(OutputStream stream) throws IOException {
stream.write(getFormData());
}
}
protected static class MultipartWriter extends RequestWriter {
private static final String MULTIPART_CONTENT_TYPE = "multipart/form-data; boundary="; //$NON-NLS-1$
/**
* The pool of ASCII chars to be used for generating a multipart
* boundary.
*/
private static final byte[] BOUNDARY_CHARS = toAsciiBytes(
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); //$NON-NLS-1$
/** Carriage return/linefeed as a byte array */
private static final byte[] CRLF = toAsciiBytes("\r\n"); //$NON-NLS-1$
/** Content dispostion as a byte array */
private static final byte[] QUOTE = toAsciiBytes("\""); //$NON-NLS-1$
/** Extra characters as a byte array */
private static final byte[] EXTRA = toAsciiBytes("--"); //$NON-NLS-1$
/** Content dispostion as a byte array */
private static final byte[] CONTENT_DISPOSITION = toAsciiBytes(
"Content-Disposition: form-data; name="); //$NON-NLS-1$
/** Content type header as a byte array */
private static final byte[] CONTENT_TYPE = toAsciiBytes(
"Content-Type: "); //$NON-NLS-1$
/** Content charset as a byte array */
private static final byte[] CHARSET = toAsciiBytes("; charset=utf-8"); //$NON-NLS-1$
/** Content type header as a byte array */
private static final byte[] CONTENT_TRANSFER_ENCODING = toAsciiBytes(
"Content-Transfer-Encoding: "); //$NON-NLS-1$
/** Attachment's file name as a byte array */
private static final byte[] FILE_NAME = toAsciiBytes("; filename="); //$NON-NLS-1$
private static final byte[] FILE_CONTENT_TYPE = toAsciiBytes(
"application/octet-stream"); //$NON-NLS-1$
private static final byte[] TEXT_CONTENT_TYPE = toAsciiBytes(
"text/plain"); //$NON-NLS-1$
private static final byte[] FILE_TRANSFER_ENCODING = toAsciiBytes(
"binary"); //$NON-NLS-1$
private static final byte[] TEXT_TRANSFER_ENCODING = toAsciiBytes(
"8bit"); //$NON-NLS-1$
private byte[] boundary = null;
private byte[] getBoundary() {
if (boundary != null)
return boundary;
Random rand = new Random();
byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size
// from 30 to 40
for (int i = 0; i < bytes.length; i++) {
bytes[i] = BOUNDARY_CHARS[rand.nextInt(BOUNDARY_CHARS.length)];
}
boundary = bytes;
return boundary;
}
public String getContentType() {
StringBuffer typeBuffer = new StringBuffer(MULTIPART_CONTENT_TYPE);
typeBuffer.append(EncodingUtils.toAsciiString(getBoundary()));
return typeBuffer.toString();
}
public long getContentLength() {
if (getParameters().isEmpty())
return 0;
long length = 0;
for (NamedValue part : getParameters()) {
length += EXTRA.length;
length += getBoundary().length;
length += CRLF.length;
length += CONTENT_DISPOSITION.length;
length += QUOTE.length;
length += toAsciiBytes(part.getEncodedName()).length;
length += QUOTE.length;
length += CRLF.length;
if (part.value instanceof File) {
length += FILE_NAME.length;
length += QUOTE.length;
length += toAsciiBytes(part.getEncodedName()).length;
length += QUOTE.length;
}
length += CONTENT_TYPE.length;
length += getContentType(part.value).length;
length += CHARSET.length;
length += CRLF.length;
length += CONTENT_TRANSFER_ENCODING.length;
length += getTransferEncoding(part.value).length;
length += CRLF.length;
length += CRLF.length;
length += getPartDataLength(part);
length += CRLF.length;
}
length += EXTRA.length;
length += getBoundary().length;
length += EXTRA.length;
length += CRLF.length;
return length;
}
public void write(OutputStream stream) throws IOException {
if (getParameters().isEmpty())
return;
for (NamedValue part : getParameters()) {
stream.write(EXTRA);
stream.write(getBoundary());
stream.write(CRLF);
stream.write(CONTENT_DISPOSITION);
stream.write(QUOTE);
stream.write(toAsciiBytes(part.getEncodedName()));
stream.write(QUOTE);
if (part.value instanceof File) {
stream.write(FILE_NAME);
stream.write(QUOTE);
stream.write(toAsciiBytes(((File) part.value).getName()));
stream.write(QUOTE);
}
stream.write(CRLF);
stream.write(CONTENT_TYPE);
stream.write(getContentType(part.value));
stream.write(CHARSET);
stream.write(CRLF);
stream.write(CONTENT_TRANSFER_ENCODING);
stream.write(getTransferEncoding(part.value));
stream.write(CRLF);
stream.write(CRLF);
writePartData(stream, part);
stream.write(CRLF);
try {
Thread.sleep(0);
} catch (InterruptedException e) {
throw new OperationCanceledException();
}
}
stream.write(EXTRA);
stream.write(getBoundary());
stream.write(EXTRA);
stream.write(CRLF);
}
private static byte[] getContentType(Object value) {
if (value instanceof File)
return FILE_CONTENT_TYPE;
return TEXT_CONTENT_TYPE;
}
private static byte[] getTransferEncoding(Object value) {
if (value instanceof File)
return FILE_TRANSFER_ENCODING;
return TEXT_TRANSFER_ENCODING;
}
private static long getPartDataLength(NamedValue part) {
if (part.value instanceof File) {
return (int) ((File) part.value).length();
}
return toAsciiBytes(part.getValue()).length;
}
private static void writePartData(OutputStream stream, NamedValue part)
throws IOException {
if (part.value instanceof File) {
writeFromFile(stream, (File) part.value);
} else {
writeFromText(stream, part.getValue());
}
}
private static void writeFromFile(OutputStream writeStream, File file)
throws IOException {
FileInputStream readStream = new FileInputStream(file);
try {
byte[] buffer = new byte[4096];
int bytes;
while ((bytes = readStream.read(buffer)) >= 0) {
writeStream.write(buffer, 0, bytes);
try {
Thread.sleep(0);
} catch (InterruptedException e) {
throw new OperationCanceledException();
}
}
} finally {
readStream.close();
}
}
private static void writeFromText(OutputStream writeStream,
String encodedText) throws IOException {
// writeStream.write(toAsciiBytes(encodedText));
writeStream.write(encodedText.getBytes("UTF-8")); //$NON-NLS-1$
}
}
private boolean https;
private String method = null;
private String uri = null;
private String domain = DEFAULT_DOMAIN;
private String path = null;
private List<NamedValue> requestHeaders = new ArrayList<NamedValue>();
private List<NamedValue> params = new ArrayList<NamedValue>();
private boolean multipart = false;
private File targetFile = null;
private int statusCode = HTTP_PREPARING;
private String responseText = null;
private IDataStore data = null;
private List<NamedValue> responseHeaders = new ArrayList<NamedValue>();
private boolean aborted = false;
private Throwable error = null;
private List<IRequestStatusChangeListener> statusChangeListeners = new ArrayList<IRequestStatusChangeListener>();
private boolean debugging = DEBUG_ALL
|| System.getProperty("org.xmind.debug.httprequests") != null; //$NON-NLS-1$
private long totalBytes = 0;
private long transferedBytes = 0;
private Thread runningThread = null;
public XMindNetRequest() {
this(false);
}
public XMindNetRequest(boolean useHTTPS) {
this.https = useHTTPS;
}
/**
* Sets the URI of this request.
* <p>
* Note that setting this value will override all <code>https</code> /
* <code>domain</code> / <code>path</code> settings.
*
* @param uri
* @return
*/
public XMindNetRequest uri(String uri) {
this.uri = uri;
return this;
}
/**
* Sets the absolute path of this request's URI.
* <p>
* The <code>path</code> should start with a "/", and may contain formatting
* tags supported by <code>java.util.Formatter</code>. The URI will be
* formatted as "[scheme]://[domain][path]".
*
* @param path
* the path of this request's URI
* @param values
* objects to be formatted into <code>path</code>
* @return
*/
public XMindNetRequest path(String path, Object... values) {
this.path = EncodingUtils.format(path, values);
return this;
}
protected XMindNetRequest domain(String domain) {
this.domain = domain;
return this;
}
public XMindNetRequest useHTTPS() {
this.https = true;
return this;
}
public XMindNetRequest multipart() {
this.multipart = true;
return this;
}
public XMindNetRequest setAuthToken(String authToken) {
return this.addHeader("AuthToken", authToken); //$NON-NLS-1$
}
public XMindNetRequest addHeader(String name, String value) {
requestHeaders.add(new NamedValue(name, value));
return this;
}
public XMindNetRequest addParameter(String name, Object value) {
params.add(new NamedValue(name, value));
return this;
}
public String getMethod() {
return method;
}
public String getURI() {
StringBuffer uriBuilder = new StringBuffer(50);
if (uri != null) {
uriBuilder.append(uri);
} else if (path != null) {
String domain = this.domain == null ? DEFAULT_DOMAIN : this.domain;
if (https) {
uriBuilder.append("https://"); //$NON-NLS-1$
} else {
uriBuilder.append("http://"); //$NON-NLS-1$
}
uriBuilder.append(domain);
uriBuilder.append(path);
} else {
return null;
}
if (!WRITABLE_METHODS.contains(method) && !params.isEmpty()) {
int i = uriBuilder.indexOf("?"); //$NON-NLS-1$
if (i >= 0) {
if (i < uriBuilder.length() - 1) {
uriBuilder.append('&');
} else {
// append nothing if '?' is the last character.
}
} else {
uriBuilder.append('?');
}
uriBuilder.append(toQueryString(params));
}
return uriBuilder.toString();
}
/**
* @return the totalBytes
*/
public long getTotalBytes() {
return totalBytes;
}
/**
* @return the transferedBytes
*/
public long getTransferedBytes() {
return transferedBytes;
}
/**
* Set the target file where the response body will be stored.
* <p>
* Note that setting the target file to non-null will cause both
* getResponseText() and getData() return null should the request succeed.
*
* @param file
* the target file
* @return this request
*/
public XMindNetRequest setTargetFile(File file) {
this.targetFile = file;
return this;
}
public File getTargetFile() {
return targetFile;
}
public void abort() {
this.aborted = true;
Thread theThread = this.runningThread;
if (theThread != null) {
theThread.interrupt();
}
}
public boolean isAborted() {
return aborted;
}
public XMindNetRequest head() {
this.method = HEAD;
return execute();
}
public XMindNetRequest get() {
this.method = GET;
return execute();
}
public XMindNetRequest put() {
this.method = PUT;
return execute();
}
public XMindNetRequest delete() {
this.method = DELETE;
return execute();
}
public XMindNetRequest post() {
this.method = POST;
return execute();
}
public int getStatusCode() {
return statusCode;
}
public IDataStore getData() {
return data;
}
public String getResponseText() {
return responseText;
}
public List<String> getAllResponseHeaders() {
List<String> keys = new ArrayList<String>(responseHeaders.size());
for (NamedValue header : responseHeaders) {
keys.add(header.name);
}
return keys;
}
public String getResponseHeader(String name) {
if (name != null) {
for (NamedValue header : responseHeaders) {
if (name.equalsIgnoreCase(header.name))
return header.getValue();
}
}
return null;
}
public boolean isRunning() {
return runningThread != null;
}
protected synchronized XMindNetRequest execute() {
runningThread = Thread.currentThread();
try {
if (isAborted())
return this;
if (method == null)
throw new IllegalStateException(
"Invalid HTTP Request: no method specified"); //$NON-NLS-1$
final String uri = getURI();
if (uri == null)
throw new IllegalStateException(
"Invalid HTTP Request: no URI/path specified"); //$NON-NLS-1$
final RequestWriter writer = createRequestWriter();
if (isAborted())
return this;
Thread thread = new Thread(new Runnable() {
public void run() {
executeInDaemonThread(uri, writer);
}
}, "XMindNetRequestConnection:" + uri); //$NON-NLS-1$
thread.setDaemon(true);
thread.setPriority(
(Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
// probably aborted by user
}
return this;
} finally {
runningThread = null;
}
}
private void executeInDaemonThread(String uri, RequestWriter writer) {
error = null;
try {
if (isAborted())
return;
debug("HTTP Request: (Prepared) %s %s\r\n%s", method, uri, //$NON-NLS-1$
requestHeaders);
send(uri, writer);
if (isAborted())
return;
} catch (OperationCanceledException e) {
debug("HTTP Request: (Aborted) %s %s", method, uri); //$NON-NLS-1$
if (!isAborted()) {
abort();
}
} catch (Throwable e) {
if (!isAborted()) {
error = e;
debug("HTTP Request: (Error: %s) %s %s", e, method, uri); //$NON-NLS-1$
}
}
}
protected void send(String uri, RequestWriter writer) throws IOException {
this.data = null;
this.responseText = null;
this.responseHeaders.clear();
setStatusCode(HTTP_CONNECTING);
debug("HTTP Request: (Connecting...) %s %s", method, uri); //$NON-NLS-1$
if (isAborted())
throw new OperationCanceledException();
URL url = new URL(uri);
if (isAborted())
throw new OperationCanceledException();
HttpURLConnection connection;
Proxy proxy = getProxy(uri);
if (proxy != null) {
debug("HTTP Request: (Applying proxy %s) %s %s", proxy, method, //$NON-NLS-1$
uri);
connection = (HttpURLConnection) url.openConnection(proxy);
} else {
connection = (HttpURLConnection) url.openConnection();
}
if (isAborted())
throw new OperationCanceledException();
if (DEBUG_ASSC) {
assc(connection);
if (isAborted())
throw new OperationCanceledException();
}
setStatusCode(HTTP_SENDING);
debug("HTTP Request: (Sending data...) %s %s", method, uri); //$NON-NLS-1$
if (isAborted())
throw new OperationCanceledException();
try {
connection.setDoOutput(writer != null);
if (isAborted())
throw new OperationCanceledException();
connection.setRequestMethod(method);
if (isAborted())
throw new OperationCanceledException();
if (writer != null) {
writer.init(params);
}
if (isAborted())
throw new OperationCanceledException();
writeHeaders(uri, connection, writer);
if (isAborted())
throw new OperationCanceledException();
if (writer != null) {
writeBody(uri, connection, writer);
}
if (isAborted())
throw new OperationCanceledException();
setStatusCode(HTTP_WAITING);
debug("HTTP Request: (Waiting...) %s %s", method, uri); //$NON-NLS-1$
if (isAborted())
throw new OperationCanceledException();
readResponse(uri, connection, connection.getInputStream(),
connection.getResponseCode());
if (isAborted())
throw new OperationCanceledException();
} catch (IOException e) {
if (isAborted())
throw new OperationCanceledException();
readResponse(uri, connection, connection.getErrorStream(),
connection.getResponseCode());
if (isAborted())
throw new OperationCanceledException();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
protected Proxy getProxy(String uri) {
/*
* Return null and let HttpURLConnection read proxy settings from system
* properties. Note that plugin 'org.eclipse.core.net' writes its proxy
* settings to system properties when it's activated, so if you relies
* on this plugin to provide proxy settings please make sure to activate
* it before making any http request.
*/
return null;
}
public Throwable getError() {
return error;
}
private RequestWriter createRequestWriter() {
if (WRITABLE_METHODS.contains(method)) {
if (multipart)
return new MultipartWriter();
return new FormSubmitter();
}
return null;
}
protected void writeHeaders(String uri, URLConnection connection,
RequestWriter writer) {
List<NamedValue> writtenHeaders = new ArrayList<NamedValue>();
Object accept = null;
for (NamedValue header : requestHeaders) {
writeHeader(connection, header.name, header.getValue(),
writtenHeaders);
if ("Accept".equalsIgnoreCase(header.name)) //$NON-NLS-1$
accept = header.value;
}
if (accept == null || "".equals(accept)) { //$NON-NLS-1$
writeHeader(connection, "Accept", "application/json", //$NON-NLS-1$//$NON-NLS-2$
writtenHeaders);
}
writeHeader(connection, "X-Client-ID", getClientId(), writtenHeaders); //$NON-NLS-1$
if (writer != null) {
writeHeader(connection, "Content-Type", //$NON-NLS-1$
writer.getContentType(), writtenHeaders);
writeHeader(connection, "Content-Length", //$NON-NLS-1$
String.valueOf(writer.getContentLength()), writtenHeaders);
}
debug("HTTP Request: (Headers written) %s %s\r\n%s", method, uri, //$NON-NLS-1$
writtenHeaders);
}
private String getClientId() {
return "xmind_v3.4.1"; //$NON-NLS-1$
}
protected void writeHeader(URLConnection connection, String key,
String value, List<NamedValue> headers) {
connection.setRequestProperty(key, value);
headers.add(new NamedValue(key, value));
}
private void writeBody(String uri, URLConnection connection,
RequestWriter writer) throws IOException {
OutputStream writeStream = connection.getOutputStream();
if (isAborted())
throw new OperationCanceledException();
BufferedOutputStream bufferedWriteStream = new BufferedOutputStream(
writeStream);
if (isAborted())
throw new OperationCanceledException();
writer.write(bufferedWriteStream);
if (isAborted())
throw new OperationCanceledException();
bufferedWriteStream.flush();
writeStream.flush();
}
protected static String toQueryString(List<NamedValue> parameters) {
StringBuffer buffer = new StringBuffer(parameters.size() * 15);
for (int i = 0; i < parameters.size(); i++) {
NamedValue param = parameters.get(i);
if (i > 0) {
buffer.append('&');
}
buffer.append(param.getEncodedName());
buffer.append('=');
buffer.append(param.getEncodedValue());
}
return buffer.toString();
}
protected void readResponse(String uri, URLConnection connection,
InputStream readStream, int responseCode) throws IOException {
this.responseText = null;
this.data = null;
this.responseHeaders.clear();
if (responseCode < 0) {
responseCode = HTTP_ERROR;
}
try {
readResponseHeaders(connection);
if (isAborted())
throw new OperationCanceledException();
if (responseCode == HTTP_ERROR) {
setStatusCode(responseCode);
debug("HTTP Response: (Unknown error) %s %s", method, uri); //$NON-NLS-1$
} else {
this.totalBytes = 0;
this.transferedBytes = 0;
String length = getResponseHeader("Content-Length"); //$NON-NLS-1$
if (length != null) {
try {
this.totalBytes = Long.parseLong(length, 10);
} catch (NumberFormatException e) {
}
}
if (isAborted())
throw new OperationCanceledException();
if (this.totalBytes == 0) {
this.totalBytes = readStream.available();
}
if (isAborted())
throw new OperationCanceledException();
setStatusCode(HTTP_RECEIVING);
debug("HTTP Request: (Receiving data, total %s bytes...) [%s] %s %s", //$NON-NLS-1$
totalBytes, responseCode, method, uri);
if (isAborted())
throw new OperationCanceledException();
if (targetFile != null && responseCode >= 200
&& responseCode < 300) {
saveTargetFile(readStream);
setStatusCode(responseCode);
debug("HTTP Request: (Response) [%s] %s %s\r\n%s\r\nSaved to '%s' (%s bytes).", //$NON-NLS-1$
getStatusCode(), method, uri, responseHeaders,
targetFile.getAbsolutePath(), targetFile.length());
} else {
int wrappedResponseCode = readResponseData(readStream);
if (wrappedResponseCode >= 100) {
setStatusCode(wrappedResponseCode);
} else {
setStatusCode(responseCode);
}
debug("HTTP Request: (Response) [%s] %s %s\r\n%s\r\n%s", //$NON-NLS-1$
responseCode, method, uri, responseHeaders,
responseText);
}
}
} finally {
if (statusCode < 100 && responseCode >= 100) {
setStatusCode(responseCode);
}
}
}
/**
* @param connection
*/
private int readResponseData(InputStream readStream) throws IOException {
this.responseText = readResponseText(readStream);
if (!"".equals(this.responseText)) { //$NON-NLS-1$
String respType = getResponseHeader("Content-Type"); //$NON-NLS-1$
if (respType != null && respType.indexOf("application/json") >= 0) { //$NON-NLS-1$
try {
this.data = new JSONStore(
new JSONObject(this.responseText));
if (this.data.has("_code")) { //$NON-NLS-1$
return this.data.getInt("_code"); //$NON-NLS-1$
}
} catch (JSONException e) {
this.error = e;
}
}
}
return 0;
}
private String readResponseText(InputStream readStream) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(
Math.max((int) totalBytes, 1024));
transfer(readStream, bytes);
return bytes.toString("utf-8"); //$NON-NLS-1$
}
/**
* @param connection
*/
private void saveTargetFile(InputStream readStream) throws IOException {
File file = this.targetFile;
File dir = file.getParentFile();
if (!dir.exists()) {
dir.mkdirs();
}
OutputStream fileWriteStream = new FileOutputStream(file);
try {
transfer(readStream, fileWriteStream);
} finally {
fileWriteStream.close();
}
}
protected void transfer(InputStream readStream, OutputStream writeStream)
throws IOException {
transfer(readStream, writeStream, 1024);
}
protected void transfer(InputStream readStream, OutputStream writeStream,
int bufSize) throws IOException {
if (bufSize <= 0)
bufSize = 1024;
byte[] buffer = new byte[bufSize];
int bytes;
while ((bytes = readStream.read(buffer)) >= 0) {
writeStream.write(buffer, 0, bytes);
this.transferedBytes += bytes;
if (isAborted())
throw new OperationCanceledException();
try {
Thread.sleep(0);
} catch (InterruptedException e) {
throw new OperationCanceledException();
}
}
}
/**
* @param connection
*/
private void readResponseHeaders(URLConnection connection) {
// Skip status line:
connection.getHeaderField(0);
// Start from 2nd header line:
int i = 1;
String key, value;
while ((key = connection.getHeaderFieldKey(i)) != null) {
value = connection.getHeaderField(i);
responseHeaders.add(new NamedValue(key, value));
i++;
}
}
public XMindNetRequest debug() {
this.debugging = true;
return this;
}
public void addStatusChangeListener(IRequestStatusChangeListener listener) {
statusChangeListeners.add(listener);
}
public void removeStatusChangeListener(
IRequestStatusChangeListener listener) {
statusChangeListeners.remove(listener);
}
protected void setStatusCode(int newStatus) {
if (newStatus == this.statusCode)
return;
int oldStatus = this.statusCode;
this.statusCode = newStatus;
fireStatusChanged(oldStatus);
}
protected void fireStatusChanged(final int oldStatus) {
final int newStatus = this.statusCode;
IRequestStatusChangeListener[] listeners = statusChangeListeners
.toArray(new IRequestStatusChangeListener[statusChangeListeners
.size()]);
for (int i = 0; i < listeners.length; i++) {
try {
listeners[i].requestStatusChanged(this, oldStatus, newStatus);
} catch (Throwable e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING,
Activator.PLUGIN_ID,
"Error occurred when notifying request status change.", //$NON-NLS-1$
e));
}
}
}
protected void debug(String format, Object... values) {
if (!debugging)
return;
if (DEBUG_TO_STDOUT) {
System.out.println(String.format(format, values));
} else {
Activator.log(String.format(format, values));
}
}
private static void assc(HttpURLConnection connection) {
try {
TrustModifier.relaxHostChecking(connection);
} catch (Exception e) {
if (DEBUG_TO_STDOUT) {
e.printStackTrace();
System.err.println("Failed to relax host checking."); //$NON-NLS-1$
} else {
Activator.getDefault().getLog()
.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID,
"Failed to relax host checking.", e)); //$NON-NLS-1$
}
}
}
/**
* A solution to accept self-signed SSL certificates.
*
* <p>
* Source came from Craig Flichel's post <a href=
* "http://www.javacodegeeks.com/2011/12/ignoring-self-signed-certificates-in.html"
* >Ignoring Self-Signed Certificates in Java</a> on Dec 1, 2011.
* </p>
*
* @author Craig Flichel
*/
private static class TrustModifier {
private static final TrustingHostnameVerifier TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier();
private static SSLSocketFactory factory;
/**
* Call this with any HttpURLConnection, and it will modify the trust
* settings if it is an HTTPS connection.
*/
public static void relaxHostChecking(HttpURLConnection conn)
throws KeyManagementException, NoSuchAlgorithmException,
KeyStoreException {
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) conn;
SSLSocketFactory factory = prepFactory(httpsConnection);
httpsConnection.setSSLSocketFactory(factory);
httpsConnection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER);
}
}
static synchronized SSLSocketFactory prepFactory(
HttpsURLConnection httpsConnection)
throws NoSuchAlgorithmException, KeyStoreException,
KeyManagementException {
if (factory == null) {
SSLContext ctx = SSLContext.getInstance("TLS"); //$NON-NLS-1$
ctx.init(null, new TrustManager[] { new AlwaysTrustManager() },
null);
factory = ctx.getSocketFactory();
}
return factory;
}
private static final class TrustingHostnameVerifier
implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
private static class AlwaysTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
}