aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java')
-rw-r--r--WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java713
1 files changed, 713 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java
new file mode 100644
index 000000000..5d6eb8dbe
--- /dev/null
+++ b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java
@@ -0,0 +1,713 @@
+package org.xmlrpc.android;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Xml;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.entity.FileEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.util.EntityUtils;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.AccountHelper;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.CoreEvents;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.util.WPUrlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlrpc.android.ApiHelper.Method;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * A WordPress XMLRPC Client.
+ * Based on android-xmlrpc: code.google.com/p/android-xmlrpc/
+ * Async support based on aXMLRPC: https://github.com/timroes/aXMLRPC
+ */
+
+public class XMLRPCClient implements XMLRPCClientInterface {
+ public static final int DEFAULT_CONNECTION_TIMEOUT_MS = 30000;
+ public static final int DEFAULT_SOCKET_TIMEOUT_MS = 60000;
+
+ public interface OnBytesUploadedListener {
+ public void onBytesUploaded(long uploadedBytes);
+ }
+
+ private static final String TAG_METHOD_CALL = "methodCall";
+ private static final String TAG_METHOD_NAME = "methodName";
+ private static final String TAG_METHOD_RESPONSE = "methodResponse";
+ private static final String TAG_PARAMS = "params";
+ private static final String TAG_PARAM = "param";
+ private static final String TAG_FAULT = "fault";
+ private static final String TAG_FAULT_CODE = "faultCode";
+ private static final String TAG_FAULT_STRING = "faultString";
+
+ private Map<Long,Caller> backgroundCalls = new HashMap<Long, Caller>();
+
+ private DefaultHttpClient mClient;
+ private OnBytesUploadedListener mOnBytesUploadedListener;
+ private HttpPost mPostMethod;
+ private XmlSerializer mSerializer;
+ private HttpParams mHttpParams;
+ private LoggedInputStream mLoggedInputStream;
+
+ private boolean mIsWpcom;
+
+ /**
+ * XMLRPCClient constructor. Creates new instance based on server URI
+ * @param uri xml-rpc server URI
+ */
+ public XMLRPCClient(URI uri, String httpuser, String httppasswd) {
+ mPostMethod = new HttpPost(uri);
+ mPostMethod.addHeader("Content-Type", "text/xml");
+ mPostMethod.addHeader("charset", "UTF-8");
+ mPostMethod.addHeader("User-Agent", WordPress.getUserAgent());
+ addWPComAuthorizationHeaderIfNeeded();
+
+ mHttpParams = mPostMethod.getParams();
+ HttpProtocolParams.setUseExpectContinue(mHttpParams, false);
+
+ UsernamePasswordCredentials credentials = null;
+ if (!TextUtils.isEmpty(httpuser) && !TextUtils.isEmpty(httppasswd)) {
+ credentials = new UsernamePasswordCredentials(httpuser, httppasswd);
+ }
+
+ mClient = instantiateClientForUri(uri, credentials);
+ mSerializer = Xml.newSerializer();
+ }
+
+ public String getResponse() {
+ if (mLoggedInputStream == null) {
+ return "";
+ }
+ return mLoggedInputStream.getResponseDocument();
+ }
+
+ private class ConnectionClient extends DefaultHttpClient {
+ public ConnectionClient(int port) throws IOException, GeneralSecurityException {
+ super();
+ TrustUserSSLCertsSocketFactory tasslf = new TrustUserSSLCertsSocketFactory();
+ Scheme scheme = new Scheme("https", tasslf, port);
+ getConnectionManager().getSchemeRegistry().register(scheme);
+ }
+ }
+
+ private DefaultHttpClient instantiateClientForUri(URI uri, UsernamePasswordCredentials usernamePasswordCredentials) {
+ DefaultHttpClient client = null;
+ if (WPUrlUtils.isWordPressCom(uri)) {
+ mIsWpcom = true;
+ }
+ if (mIsWpcom) {
+ //wpcom blog or self-hosted blog on plain HTTP
+ client = new DefaultHttpClient();
+ } else {
+ int port = uri.getPort();
+ if (port == -1) {
+ port = 443;
+ }
+
+ try {
+ client = new ConnectionClient(port);
+ } catch (GeneralSecurityException e) {
+ AppLog.e(T.API, "Cannot create the DefaultHttpClient object with our TrustUserSSLCertsSocketFactory", e);
+ client = null;
+ } catch (IOException e) {
+ AppLog.e(T.API, "Cannot create the DefaultHttpClient object with our TrustUserSSLCertsSocketFactory", e);
+ client = null;
+ }
+
+ if (client == null) {
+ client = new DefaultHttpClient();
+ }
+ }
+
+ HttpConnectionParams.setConnectionTimeout(client.getParams(), DEFAULT_CONNECTION_TIMEOUT_MS);
+ HttpConnectionParams.setSoTimeout(client.getParams(), DEFAULT_SOCKET_TIMEOUT_MS);
+
+ // Setup HTTP Basic Auth if necessary
+ if (usernamePasswordCredentials != null) {
+ BasicCredentialsProvider cP = new BasicCredentialsProvider();
+ cP.setCredentials(AuthScope.ANY, usernamePasswordCredentials);
+ client.setCredentialsProvider(cP);
+ }
+
+ return client;
+ }
+
+ public void addQuickPostHeader(String type) {
+ mPostMethod.addHeader("WP-QUICK-POST", type);
+ }
+
+ /**
+ * Convenience constructor. Creates new instance based on server String address
+ * @param url server url
+ */
+ public XMLRPCClient(String url, String httpuser, String httppasswd) {
+ this(URI.create(url), httpuser, httppasswd);
+ }
+
+ /**
+ * Convenience XMLRPCClient constructor. Creates new instance based on server URL
+ * @param url server URL
+ */
+ public XMLRPCClient(URL url, String httpuser, String httppasswd) {
+ this(URI.create(url.toExternalForm()), httpuser, httppasswd);
+ }
+
+ /**
+ * Set WP.com auth header
+ * @param authToken authorization token
+ */
+ public void setAuthorizationHeader(String authToken) {
+ if( authToken != null)
+ mPostMethod.addHeader("Authorization", String.format("Bearer %s", authToken));
+ else
+ mPostMethod.removeHeaders("Authorization");
+ }
+
+ /**
+ * Call method with optional parameters. This is general method.
+ * If you want to call your method with 0-8 parameters, you can use more
+ * convenience call methods
+ *
+ * @param method name of method to call
+ * @param params parameters to pass to method (may be null if method has no parameters)
+ * @return deserialized method return value
+ * @throws XMLRPCException
+ */
+ public Object call(String method, Object[] params) throws XMLRPCException, IOException, XmlPullParserException {
+ return call(method, params, null);
+ }
+
+ /**
+ * Convenience method call with no parameters
+ *
+ * @param method name of method to call
+ * @return deserialized method return value
+ * @throws XMLRPCException
+ */
+ public Object call(String method) throws XMLRPCException, IOException, XmlPullParserException {
+ return call(method, null, null);
+ }
+
+
+ public Object call(String method, Object[] params, File tempFile) throws XMLRPCException, IOException, XmlPullParserException {
+ return new Caller().callXMLRPC(method, params, tempFile);
+ }
+
+ /**
+ * Convenience call for callAsync with two paramaters
+ *
+ * @param listener, methodName, parameters
+ * @return unique id of this async call
+ * @throws XMLRPCException
+ */
+ public long callAsync(XMLRPCCallback listener, String methodName, Object[] params) {
+ return callAsync(listener, methodName, params, null);
+ }
+
+ /**
+ * Asynchronous XMLRPC call
+ *
+ * @param listener, XMLRPC methodName, XMLRPC parameters, File for large uploads
+ * @return unique id of this async call
+ * @throws XMLRPCException
+ */
+ public long callAsync(XMLRPCCallback listener, String methodName, Object[] params, File tempFile) {
+ long id = System.currentTimeMillis();
+ new Caller(listener, id, methodName, params, tempFile).start();
+ return id;
+ }
+
+ /**
+ * Cancel the current call
+ */
+ public void cancel() {
+ mPostMethod.abort();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Object parseXMLRPCResponse(InputStream is, HttpEntity entity)
+ throws XMLRPCException, IOException, XmlPullParserException, NumberFormatException {
+ // setup pull parser
+ XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
+
+ // Many WordPress configs can output junk before the xml response (php warnings for example), this cleans it.
+ int bomCheck = -1;
+ int stopper = 0;
+ while ((bomCheck = is.read()) != -1 && stopper <= 5000) {
+ stopper++;
+ String snippet = "";
+ // 60 == '<' character
+ if (bomCheck == 60) {
+ for (int i = 0; i < 4; i++) {
+ byte[] chunk = new byte[1];
+ is.read(chunk);
+ snippet += new String(chunk, "UTF-8");
+ }
+ if (snippet.equals("?xml")) {
+ // it's all good, add xml tag back and start parsing
+ String start = "<" + snippet;
+ List<InputStream> streams = Arrays.asList(new ByteArrayInputStream(start.getBytes()), is);
+ is = new SequenceInputStream(Collections.enumeration(streams));
+ break;
+ } else {
+ // keep searching...
+ List<InputStream> streams = Arrays.asList(new ByteArrayInputStream(snippet.getBytes()), is);
+ is = new SequenceInputStream(Collections.enumeration(streams));
+ }
+ }
+ }
+
+ pullParser.setInput(is, "UTF-8");
+
+ // lets start pulling...
+ pullParser.nextTag();
+ pullParser.require(XmlPullParser.START_TAG, null, TAG_METHOD_RESPONSE);
+
+ pullParser.nextTag(); // either TAG_PARAMS (<params>) or TAG_FAULT (<fault>)
+ String tag = pullParser.getName();
+ if (tag.equals(TAG_PARAMS)) {
+ // normal response
+ pullParser.nextTag(); // TAG_PARAM (<param>)
+ pullParser.require(XmlPullParser.START_TAG, null, TAG_PARAM);
+ pullParser.nextTag(); // TAG_VALUE (<value>)
+ // no parser.require() here since its called in XMLRPCSerializer.deserialize() below
+ // deserialize result
+ Object obj = XMLRPCSerializer.deserialize(pullParser);
+ consumeHttpEntity(entity);
+ return obj;
+ } else if (tag.equals(TAG_FAULT)) {
+ // fault response
+ pullParser.nextTag(); // TAG_VALUE (<value>)
+ // no parser.require() here since its called in XMLRPCSerializer.deserialize() below
+ // deserialize fault result
+ Map<String, Object> map = (Map<String, Object>) XMLRPCSerializer.deserialize(pullParser);
+ consumeHttpEntity(entity);
+ //Check that required tags are in the response
+ if (!map.containsKey(TAG_FAULT_STRING) || !map.containsKey(TAG_FAULT_CODE)) {
+ throw new XMLRPCException("Bad XMLRPC Fault response received - <faultCode> and/or <faultString> missing!");
+ }
+ String faultString = String.valueOf(map.get(TAG_FAULT_STRING));
+ int faultCode;
+ try {
+ faultCode = (int) map.get(TAG_FAULT_CODE);
+ } catch (NumberFormatException | ClassCastException e) {
+ throw new XMLRPCException("Bad XMLRPC Fault response received - <faultCode> value is not a valid integer");
+ }
+ throw new XMLRPCFault(faultString, faultCode);
+ } else {
+ consumeHttpEntity(entity);
+ throw new XMLRPCException("Bad tag <" + tag + "> in XMLRPC response - neither <params> nor <fault>");
+ }
+ }
+
+ /**
+ * Deallocate Http Entity and close streams
+ */
+ private static void consumeHttpEntity(HttpEntity entity) {
+ // Ideally we should use EntityUtils.consume(), introduced in apache http utils 4.1 - not available in
+ // Android yet
+ if (entity != null) {
+ try {
+ entity.consumeContent();
+ } catch (IOException e) {
+ // ignore exception (could happen if Content-Length is wrong)
+ }
+ }
+ }
+
+ public void preparePostMethod(String method, Object[] params, File tempFile) throws IOException, XMLRPCException, IllegalArgumentException, IllegalStateException {
+ // prepare POST body
+ if (method.equals(Method.UPLOAD_FILE)) {
+ if (!tempFile.exists() && !tempFile.mkdirs()) {
+ throw new XMLRPCException("Path to file could not be created.");
+ }
+
+ FileWriter fileWriter = new FileWriter(tempFile);
+ mSerializer.setOutput(fileWriter);
+
+ mSerializer.startDocument(null, null);
+ mSerializer.startTag(null, TAG_METHOD_CALL);
+ // set method name
+ mSerializer.startTag(null, TAG_METHOD_NAME).text(method).endTag(null, TAG_METHOD_NAME);
+ if (params != null && params.length != 0) {
+ // set method params
+ mSerializer.startTag(null, TAG_PARAMS);
+ for (int i = 0; i < params.length; i++) {
+ mSerializer.startTag(null, TAG_PARAM).startTag(null, XMLRPCSerializer.TAG_VALUE);
+ XMLRPCSerializer.serialize(mSerializer, params[i]);
+ mSerializer.endTag(null, XMLRPCSerializer.TAG_VALUE).endTag(null, TAG_PARAM);
+ }
+ mSerializer.endTag(null, TAG_PARAMS);
+ }
+ mSerializer.endTag(null, TAG_METHOD_CALL);
+ mSerializer.endDocument();
+
+ fileWriter.flush();
+ fileWriter.close();
+
+ FileEntity fEntity = new FileEntity(tempFile, "text/xml; charset=\"UTF-8\"") {
+ // Hook in a CountingOutputStream to keep track of bytes uploaded
+ @Override
+ public void writeTo(final OutputStream outstream) throws IOException {
+ super.writeTo(new CountingOutputStream(outstream));
+ }
+ };
+
+ fEntity.setContentType("text/xml");
+ mPostMethod.setEntity(fEntity);
+ } else {
+ StringWriter bodyWriter = new StringWriter();
+ mSerializer.setOutput(bodyWriter);
+
+ mSerializer.startDocument(null, null);
+ mSerializer.startTag(null, TAG_METHOD_CALL);
+ // set method name
+ mSerializer.startTag(null, TAG_METHOD_NAME).text(method).endTag(null, TAG_METHOD_NAME);
+ if (params != null && params.length != 0) {
+ // set method params
+ mSerializer.startTag(null, TAG_PARAMS);
+ for (int i = 0; i < params.length; i++) {
+ mSerializer.startTag(null, TAG_PARAM).startTag(null, XMLRPCSerializer.TAG_VALUE);
+ if (method.equals("metaWeblog.editPost") || method.equals("metaWeblog.newPost")) {
+ XMLRPCSerializer.serialize(mSerializer, params[i]);
+ } else {
+ XMLRPCSerializer.serialize(mSerializer, params[i]);
+ }
+ mSerializer.endTag(null, XMLRPCSerializer.TAG_VALUE).endTag(null, TAG_PARAM);
+ }
+ mSerializer.endTag(null, TAG_PARAMS);
+ }
+ mSerializer.endTag(null, TAG_METHOD_CALL);
+ mSerializer.endDocument();
+
+ HttpEntity entity = new StringEntity(bodyWriter.toString());
+ mPostMethod.setEntity(entity);
+ }
+ }
+
+ /**
+ * The Caller class is used to make asynchronous calls to the server.
+ * For synchronous calls the Thread function of this class isn't used.
+ */
+ private class Caller extends Thread {
+ private XMLRPCCallback listener;
+ private long threadId;
+ private String methodName;
+ private Object[] params;
+ private File tempFile;
+
+ /**
+ * Create a new Caller for asynchronous use.
+ *
+ * @param listener The listener to notice about the response or an error.
+ * @param threadId An id that will be send to the listener.
+ * @param methodName The method name to call.
+ * @param params The parameters of the call or null.
+ */
+ public Caller(XMLRPCCallback listener, long threadId, String methodName, Object[] params, File tempFile) {
+ this.listener = listener;
+ this.threadId = threadId;
+ this.methodName = methodName;
+ this.params = params;
+ this.tempFile = tempFile;
+ }
+
+ /**
+ * Create a new Caller for synchronous use.
+ * If the caller has been created with this constructor you cannot use the
+ * start method to start it as a thread. But you can call the call method
+ * on it for synchronous use.
+ */
+ public Caller() { }
+
+ /**
+ * The run method is invoked when the thread gets started.
+ * This will only work, if the Caller has been created with parameters.
+ * It execute the call method and notify the listener about the result.
+ */
+ @Override
+ public void run() {
+ if(listener == null)
+ return;
+
+ try {
+ backgroundCalls.put(threadId, this);
+ Object o = this.callXMLRPC(methodName, params, tempFile);
+ listener.onSuccess(threadId, o);
+ } catch(CancelException ex) {
+ // Don't notify the listener, if the call has been canceled.
+ } catch (Exception ex) {
+ listener.onFailure(threadId, ex);
+ } finally {
+ backgroundCalls.remove(threadId);
+ }
+
+ }
+
+ /**
+ * Call method with optional parameters
+ *
+ * @param method name of method to call
+ * @param params parameters to pass to method (may be null if method has no parameters)
+ * @return deserialized method return value
+ * @throws XMLRPCException
+ */
+ private Object callXMLRPC(String method, Object[] params, File tempFile)
+ throws XMLRPCException, IOException, XmlPullParserException {
+ mLoggedInputStream = null;
+ try {
+ preparePostMethod(method, params, tempFile);
+
+ // execute HTTP POST request
+ HttpResponse response = mClient.execute(mPostMethod);
+
+ if (response.getStatusLine() == null) // StatusLine is null. We can't read the response code.
+ throw new XMLRPCException( "HTTP Status code is missing!" );
+
+ int statusCode = response.getStatusLine().getStatusCode();
+ HttpEntity entity = response.getEntity();
+
+ if (entity == null) {
+ //This is an error since the parser will fail here.
+ throw new XMLRPCException( "HTTP status code: " + statusCode + " was returned AND no response from the server." );
+ }
+
+ if (statusCode == HttpStatus.SC_OK) {
+ mLoggedInputStream = new LoggedInputStream(entity.getContent());
+ return XMLRPCClient.parseXMLRPCResponse(mLoggedInputStream, entity);
+ }
+
+ String statusLineReasonPhrase = StringUtils.notNullStr(response.getStatusLine().getReasonPhrase());
+ try {
+ String responseString = EntityUtils.toString(entity, "UTF-8");
+ if (TextUtils.isEmpty(responseString)) {
+ AppLog.e(T.API, "No HTTP error document document from the server");
+ } else {
+ AppLog.e(T.API, "HTTP error document received from the server: " + responseString);
+ }
+
+ if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
+ //Try to intercept out of memory error here and show a better error message.
+ if (!TextUtils.isEmpty(responseString) && responseString.contains("php fatal error") &&
+ responseString.contains("bytes exhausted")) {
+ String newErrorMsg;
+ if (method.equals(Method.UPLOAD_FILE)) {
+ newErrorMsg =
+ "The server doesn't have enough memory to upload this file. You may need to increase the PHP memory limit on your site.";
+ } else {
+ newErrorMsg =
+ "The server doesn't have enough memory to fulfill the request. You may need to increase the PHP memory limit on your site.";
+ }
+ throw new XMLRPCException( statusLineReasonPhrase + ".\n\n" + newErrorMsg);
+ }
+ }
+
+ } catch (Exception e) {
+ // eat all the exceptions here, we dont want to crash the app when trying to show a
+ // better error message.
+ }
+ throw new XMLRPCException( "HTTP status code: " + statusCode + " was returned. " + statusLineReasonPhrase);
+ } catch (XMLRPCFault e) {
+ if (mLoggedInputStream!=null) {
+ AppLog.w(T.API, "Response document received from the server: " + mLoggedInputStream.getResponseDocument());
+ }
+ // Detect login issues and broadcast a message if the error is known
+ switch (e.getFaultCode()) {
+ case 403:
+ // Ignore 403 error from certain methods known for replying with incorrect error code on
+ // lacking permissions
+ if ("wp.getPostFormats".equals(method) || "wp.getCommentStatusList".equals(method)
+ || "wp.getPostStatusList".equals(method) || "wp.getPageStatusList".equals(method)) {
+ break;
+ }
+ EventBus.getDefault().post(new CoreEvents.InvalidCredentialsDetected());
+ break;
+ case 425:
+ EventBus.getDefault().post(new CoreEvents.TwoFactorAuthenticationDetected());
+ break;
+ //TODO: Check the login limit here
+ default:
+ break;
+ }
+ throw e;
+ } catch (XmlPullParserException e) {
+ AppLog.e(T.API, "Error while parsing the XML-RPC response document received from the server.", e);
+ if (mLoggedInputStream!=null) {
+ AppLog.e(T.API, "Response document received from the server: " + mLoggedInputStream.getResponseDocument());
+ }
+ checkXMLRPCErrorMessage(e);
+ throw e;
+ } catch (NumberFormatException e) {
+ //we can catch NumberFormatException here and re-throw an XMLRPCException.
+ //The response document is not a valid XML-RPC document after all.
+ AppLog.e(T.API, "Error while parsing the XML-RPC response document received from the server.", e);
+ if (mLoggedInputStream!=null) {
+ AppLog.e(T.API, "Response document received from the server: " + mLoggedInputStream.getResponseDocument());
+ }
+ throw new XMLRPCException("The response received contains an invalid number. " + e.getMessage());
+ } catch (XMLRPCException e) {
+ if (mLoggedInputStream!=null) {
+ AppLog.e(T.API, "Response document received from the server: " + mLoggedInputStream.getResponseDocument());
+ }
+ checkXMLRPCErrorMessage(e);
+ throw e;
+ } catch (SSLHandshakeException e) {
+ if (mIsWpcom) {
+ AppLog.e(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected on wordpress.com");
+ } else {
+ AppLog.w(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected.");
+ EventBus.getDefault().post(new CoreEvents.InvalidSslCertificateDetected());
+ }
+ throw e;
+ } catch (SSLPeerUnverifiedException e) {
+ if (mIsWpcom) {
+ AppLog.e(T.NUX, "SSLPeerUnverifiedException failed. Erroneous SSL certificate detected on wordpress.com");
+ } else {
+ AppLog.w(T.NUX, "SSLPeerUnverifiedException failed. Erroneous SSL certificate detected.");
+ EventBus.getDefault().post(new CoreEvents.InvalidSslCertificateDetected());
+ }
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } finally {
+ deleteTempFile(method, tempFile);
+ try {
+ if (mLoggedInputStream != null) {
+ mLoggedInputStream.close();
+ }
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Detect login issues and broadcast a message if the error is known, App Activities should listen to these
+ * broadcasted events and present user action to take
+ *
+ * @return true if error is known and event broadcasted, false else
+ */
+ private boolean checkXMLRPCErrorMessage(Exception exception) {
+ String errorMessage = exception.getMessage().toLowerCase();
+ if ((errorMessage.contains("code: 503") || errorMessage.contains("code 503")) &&
+ (errorMessage.contains("limit reached") || errorMessage.contains("login limit"))) {
+ EventBus.getDefault().post(new CoreEvents.LoginLimitDetected());
+ return true;
+ }
+ return false;
+ }
+
+ private void deleteTempFile(String method, File tempFile) {
+ if (tempFile != null) {
+ if ((method.equals(Method.UPLOAD_FILE))){ //get rid of the temp file
+ tempFile.delete();
+ }
+ }
+ }
+
+ private void addWPComAuthorizationHeaderIfNeeded() {
+ Context ctx = WordPress.getContext();
+ if (ctx == null) return;
+
+ if (isDotComXMLRPCEndpoint(mPostMethod.getURI())) {
+ String token = AccountHelper.getDefaultAccount().getAccessToken();
+ if (!TextUtils.isEmpty(token)) {
+ setAuthorizationHeader(token);
+ }
+ }
+ }
+
+ // Return true if wpcom XML-RPC Endpoint is called on a secure connection (https).
+ public boolean isDotComXMLRPCEndpoint(URI clientUri) {
+ if (clientUri == null) return false;
+
+ String path = clientUri.getPath();
+ String host = clientUri.getHost();
+ String protocol = clientUri.getScheme();
+ if (path == null || host == null || protocol == null) {
+ return false;
+ }
+
+ return path.equals("/xmlrpc.php") && WPUrlUtils.safeToAddWordPressComAuthToken(clientUri) && protocol.equals("https");
+ }
+
+ private class CancelException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ private class CountingOutputStream extends FilterOutputStream {
+
+ private long mTotalBytes;
+
+ CountingOutputStream(final OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ out.write(b);
+ mTotalBytes += b.length;
+
+ if (mOnBytesUploadedListener != null) {
+ mOnBytesUploadedListener.onBytesUploaded(mTotalBytes);
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ mTotalBytes += len;
+
+ if (mOnBytesUploadedListener != null) {
+ mOnBytesUploadedListener.onBytesUploaded(mTotalBytes);
+ }
+ }
+ }
+
+ public void setOnBytesUploadedListener(OnBytesUploadedListener listener) {
+ mOnBytesUploadedListener = listener;
+ }
+}