diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aeb672aabacb..1618857848b4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,6 +64,14 @@ The Android build tools now allow drawable resources to be provided exclusively
From the [release notes](http://tools.android.com/tech-docs/new-build-system):
`PNGs are generated for every vector drawable found in a resource directory that does not specify an API version (or specifies a version lower than 21).`
+# Subtree'd projects
+
+The [WordPress-HealthCheck-Common][healthcheck] project is used in the tests and loaded from `assets` on tests run. Use the following command to pull in newer commits from the external project:
+
+ $ git subtree pull --prefix=WordPress/src/androidTest/assets/health-check/ https://github.com/wordpress-mobile/WordPress-HealthCheck-Common.git develop
+
+[healthcheck]: https://github.com/wordpress-mobile/WordPress-HealthCheck-Common
+
# Contribute to translations
We use a tool called GlotPress to manage translations. The WordPress-Android GlotPress instance lives here: http://translate.wordpress.org/projects/apps/android/dev. To add new translations or fix existing ones, create an account over at GlotPress and submit your changes over at the GlotPress site.
diff --git a/WordPress/build.gradle b/WordPress/build.gradle
index 679001c32e63..35b0b065ec2e 100644
--- a/WordPress/build.gradle
+++ b/WordPress/build.gradle
@@ -95,6 +95,8 @@ dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
androidTestCompile 'org.objenesis:objenesis:2.1'
androidTestCompile 'org.mockito:mockito-core:+'
+ androidTestCompile 'com.squareup.okhttp:mockwebserver:2.7.5'
+
// Provided by the WordPress-Android Repository
compile 'org.wordpress:drag-sort-listview:0.6.1' // not found in maven central
diff --git a/WordPress/src/androidTest/assets/health-check/health-check-xplat-testcases.json b/WordPress/src/androidTest/assets/health-check/health-check-xplat-testcases.json
new file mode 100644
index 000000000000..49cb953e8e13
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/health-check-xplat-testcases.json
@@ -0,0 +1,229 @@
+{
+ "testcases": [
+ {
+ "comment": "testing empty url",
+ "realm": "URL_CANONICALIZATION",
+ "setup": {
+ "input": {
+ "siteUrl": ""
+ },
+ "output": {
+ "error": {
+ "type": "SITE_URL_CANNOT_BE_EMPTY",
+ "message": "Check that the site URL entered is valid"
+ }
+ }
+ }
+ },
+ {
+ "comment": "testing whitespace url",
+ "realm": "URL_CANONICALIZATION",
+ "setup": {
+ "input": {
+ "siteUrl": " "
+ },
+ "output": {
+ "error": {
+ "type": "SITE_URL_CANNOT_BE_EMPTY",
+ "message": "Check that the site URL entered is valid"
+ }
+ }
+ }
+ },
+ {
+ "comment": "testing null url",
+ "realm": "URL_CANONICALIZATION",
+ "setup": {
+ "input": {
+ "siteUrl": null
+ },
+ "output": {
+ "error": {
+ "type": "SITE_URL_CANNOT_BE_EMPTY",
+ "message": "Check that the site URL entered is valid"
+ }
+ }
+ }
+ },
+ {
+ "comment": "testing padding whitespace",
+ "realm": "URL_CANONICALIZATION",
+ "setup": {
+ "input": {
+ "siteUrl": " \t http://wordpress.com \t "
+ },
+ "output": {
+ "siteUrl": "http://wordpress.com"
+ }
+ }
+ },
+ {
+ "comment": "testing xmlrpc.php missing",
+ "realm": "XMLPRC_DISCOVERY",
+ "setup": {
+ "input": {
+ "serverMock": [
+ {
+ "request": {
+ "method": "GET",
+ "path": "/"
+ },
+ "response": {
+ "statusCode": 404
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "path": "/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 404
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "path": "/"
+ },
+ "response": {
+ "statusCode": 404
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "path": "/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 404
+ }
+ }
+ ]
+ },
+ "output": {
+ "error": {
+ "type": "XMLRPC_MISSING",
+ "message": "Couldn't connect to the WordPress site"
+ }
+ }
+ }
+ },
+ {
+ "comment": "testing xmlrpc.php found",
+ "realm": "XMLPRC_DISCOVERY",
+ "setup": {
+ "input": {
+ "serverMock": [
+ {
+ "request": {
+ "method": "POST",
+ "path": "/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 200,
+ "body": "asset:listMethodsResponse.xml"
+ }
+ }
+ ]
+ },
+ "output": {
+ "xmlrpcEndpoint": "http://mockserver/xmlrpc.php"
+ }
+ }
+ },
+ {
+ "comment": "testing xmlrpc.php discovered after redirect",
+ "realm": "XMLPRC_DISCOVERY",
+ "setup": {
+ "input": {
+ "siteUrl": "http://mockserver/wp",
+ "serverMock": [
+ {
+ "request": {
+ "method": "GET",
+ "path": "/wp"
+ },
+ "response": {
+ "statusCode": 301,
+ "headers": {
+ "Location": "/wpnew/"
+ },
+ "body": "Page has moved!"
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "path": "/wp"
+ },
+ "response": {
+ "statusCode": 307,
+ "headers": {
+ "Location": "/wpnew/"
+ },
+ "body": "Page has moved! POST to new address!"
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "path": "/wp/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 307,
+ "headers": {
+ "Location": "/wpnew/xmlrpc.php"
+ },
+ "body": "Page has moved! POST to new address!"
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "path": "/wpnew/"
+ },
+ "response": {
+ "statusCode": 200,
+ "body": "asset:index_with_redirect.html"
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "path": "/wpnew/xmlrpc.php?rsd"
+ },
+ "response": {
+ "statusCode": 200,
+ "body": "asset:rsd_with_redirect.xml"
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "path": "/wpnew/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 405,
+ "body": "XML-RPC server accepts POST requests only."
+ }
+ },
+ {
+ "request": {
+ "method": "POST",
+ "path": "/wpnew/xmlrpc.php"
+ },
+ "response": {
+ "statusCode": 200,
+ "body": "asset:listMethodsResponse.xml"
+ }
+ }
+ ]
+ },
+ "output": {
+ "xmlrpcEndpoint": "http://mockserver/wpnew/xmlrpc.php"
+ }
+ }
+ }
+ ]
+}
diff --git a/WordPress/src/androidTest/assets/health-check/index.html b/WordPress/src/androidTest/assets/health-check/index.html
new file mode 100644
index 000000000000..e4c7f314bb12
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/index.html
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+ wplogin – Just another WordPress site
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/androidTest/assets/health-check/index_with_redirect.html b/WordPress/src/androidTest/assets/health-check/index_with_redirect.html
new file mode 100644
index 000000000000..7c5ea729ee33
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/index_with_redirect.html
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+ wplogin – Just another WordPress site
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/androidTest/assets/health-check/listMethodsResponse.xml b/WordPress/src/androidTest/assets/health-check/listMethodsResponse.xml
new file mode 100644
index 000000000000..d7b363def971
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/listMethodsResponse.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+ system.multicall
+ system.listMethods
+ system.getCapabilities
+ demo.addTwoNumbers
+ demo.sayHello
+ pingback.extensions.getPingbacks
+ pingback.ping
+ mt.publishPost
+ mt.getTrackbackPings
+ mt.supportedTextFilters
+ mt.supportedMethods
+ mt.setPostCategories
+ mt.getPostCategories
+ mt.getRecentPostTitles
+ mt.getCategoryList
+ metaWeblog.getUsersBlogs
+ metaWeblog.deletePost
+ metaWeblog.newMediaObject
+ metaWeblog.getCategories
+ metaWeblog.getRecentPosts
+ metaWeblog.getPost
+ metaWeblog.editPost
+ metaWeblog.newPost
+ blogger.deletePost
+ blogger.editPost
+ blogger.newPost
+ blogger.getRecentPosts
+ blogger.getPost
+ blogger.getUserInfo
+ blogger.getUsersBlogs
+ wp.restoreRevision
+ wp.getRevisions
+ wp.getPostTypes
+ wp.getPostType
+ wp.getPostFormats
+ wp.getMediaLibrary
+ wp.getMediaItem
+ wp.getCommentStatusList
+ wp.newComment
+ wp.editComment
+ wp.deleteComment
+ wp.getComments
+ wp.getComment
+ wp.setOptions
+ wp.getOptions
+ wp.getPageTemplates
+ wp.getPageStatusList
+ wp.getPostStatusList
+ wp.getCommentCount
+ wp.deleteFile
+ wp.uploadFile
+ wp.suggestCategories
+ wp.deleteCategory
+ wp.newCategory
+ wp.getTags
+ wp.getCategories
+ wp.getAuthors
+ wp.getPageList
+ wp.editPage
+ wp.deletePage
+ wp.newPage
+ wp.getPages
+ wp.getPage
+ wp.editProfile
+ wp.getProfile
+ wp.getUsers
+ wp.getUser
+ wp.getTaxonomies
+ wp.getTaxonomy
+ wp.getTerms
+ wp.getTerm
+ wp.deleteTerm
+ wp.editTerm
+ wp.newTerm
+ wp.getPosts
+ wp.getPost
+ wp.deletePost
+ wp.editPost
+ wp.newPost
+ wp.getUsersBlogs
+
+
+
+
+
\ No newline at end of file
diff --git a/WordPress/src/androidTest/assets/health-check/rsd.xml b/WordPress/src/androidTest/assets/health-check/rsd.xml
new file mode 100644
index 000000000000..3ce3273938bd
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/rsd.xml
@@ -0,0 +1,14 @@
+
+
+ WordPress
+ https://wordpress.org/
+ http://mockserver
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/androidTest/assets/health-check/rsd_with_redirect.xml b/WordPress/src/androidTest/assets/health-check/rsd_with_redirect.xml
new file mode 100644
index 000000000000..4de183be479b
--- /dev/null
+++ b/WordPress/src/androidTest/assets/health-check/rsd_with_redirect.xml
@@ -0,0 +1,14 @@
+
+
+ WordPress
+ https://wordpress.org/
+ http://mockserver
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/androidTest/java/org/wordpress/android/util/HealthCheckTest.java b/WordPress/src/androidTest/java/org/wordpress/android/util/HealthCheckTest.java
new file mode 100644
index 000000000000..6f0d6a63b857
--- /dev/null
+++ b/WordPress/src/androidTest/java/org/wordpress/android/util/HealthCheckTest.java
@@ -0,0 +1,228 @@
+package org.wordpress.android.util;
+
+import com.squareup.okhttp.Headers;
+import com.squareup.okhttp.mockwebserver.Dispatcher;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.TestUtils;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.ui.accounts.helpers.FetchBlogListWPOrg;
+import org.xmlrpc.android.LoggedInputStream;
+import org.xmlrpc.android.XMLRPCUtils;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+public class HealthCheckTest extends InstrumentationTestCase {
+ private static final String sAssetPathBase = "health-check/";
+ private static final String sServerAddressMagicString = "mockserver";
+ private static final String sServerResponsesMagicScheme = "asset:";
+
+ private void setLocale(String language, String country) {
+ Locale locale = new Locale(language, country);
+ Locale.setDefault(locale);
+ Resources res = getInstrumentation().getTargetContext().getResources();
+ Configuration config = res.getConfiguration();
+ config.locale = locale;
+ res.updateConfiguration(config, res.getDisplayMetrics());
+ }
+
+ @Override
+ protected void setUp() {
+ WordPress.setupVolleyQueue();
+
+ // set the app locale to english since the tests only support English for now
+ setLocale("en", "US");
+ }
+
+ @Override
+ protected void tearDown() {
+ }
+
+ private static String stringFromAsset(Context context, String assetFilename) throws IOException {
+ LoggedInputStream mLoggedInputStream = new LoggedInputStream(context.getAssets().open(assetFilename));
+ return TestUtils.convertStreamToString(mLoggedInputStream);
+ }
+
+ private static JSONObject jsonFromAsset(Context context, String assetFilename) throws IOException, JSONException {
+ return new JSONObject(stringFromAsset(context, assetFilename));
+ }
+
+ public void testHealthCheckXplat() throws JSONException, IOException {
+ JSONArray testCases = jsonFromAsset(getInstrumentation().getContext(), sAssetPathBase +
+ "health-check-xplat-testcases.json").getJSONArray("testcases");
+
+ for (int i = 0; i < testCases.length(); i++) {
+ final JSONObject testCase = testCases.getJSONObject(i);
+ final String testCaseComment = testCase.getString("comment");
+
+ final JSONObject testSetup = testCase.getJSONObject("setup");
+ final String realm = testCase.getString("realm");
+
+ switch (realm) {
+ case "URL_CANONICALIZATION":
+ runUrlCanonicalization(testCaseComment, testSetup);
+ break;
+ case "XMLPRC_DISCOVERY":
+ runXmlrpcDiscovery(testCaseComment, testSetup);
+ break;
+ default:
+ // fail the testsuite
+ assertTrue("health-check realm " + realm + " is not supported!", false);
+ break;
+ }
+ }
+ }
+
+ private void runUrlCanonicalization(String testCaseComment, JSONObject testSetup) throws JSONException {
+ final JSONObject input = testSetup.getJSONObject("input");
+
+ final String inputUrl = input.isNull("siteUrl") ? null : input.getString("siteUrl");
+
+ final JSONObject output = testSetup.getJSONObject("output");
+
+ final String outputUrl = output.optString("siteUrl", null);
+ final JSONObject error = output.optJSONObject("error");
+
+ String canonicalizedUrl = null;
+ try {
+ canonicalizedUrl = XMLRPCUtils.sanitizeSiteUrl(inputUrl);
+
+ // if we reached this point, it means that no error occurred
+ assertNull(testCaseMessage("Testcase defines an error but no error occurred!", testCaseComment), error);
+ } catch (XMLRPCUtils.XMLRPCUtilsException hce) {
+ assertNotNull(testCaseMessage("Error occurred but testcase does not define an error!", testCaseComment),
+ error);
+
+ assertEquals(testCaseMessage("Error message does not match the defined one!", testCaseComment), error
+ .getString("message"), getInstrumentation().getTargetContext().getString(hce.errorMsgId));
+ }
+
+ assertEquals(testCaseMessage("Canonicalized URL does not match the defined one!", testCaseComment),
+ outputUrl, canonicalizedUrl);
+ }
+
+ private void runXmlrpcDiscovery(String testCaseComment, JSONObject testSetup) throws JSONException, IOException {
+ final MockWebServer server = new MockWebServer();
+
+ testSetup = new JSONObject(replaceServerMagicName(server, testSetup.toString()));
+
+ final JSONObject input = testSetup.getJSONObject("input");
+
+ setupMockHttpServer(server, input);
+
+ final String inputUrl = input.isNull("siteUrl") ? server.url("").toString() : input.getString("siteUrl");
+
+ final JSONObject output = testSetup.getJSONObject("output");
+
+ final String outputUrl = output.optString("xmlrpcEndpoint", null);
+ final JSONObject error = output.optJSONObject("error");
+
+ String xmlrpcUrl = null;
+ try {
+ xmlrpcUrl = XMLRPCUtils.verifyOrDiscoverXmlRpcUrl(inputUrl, input.optString("username", null), input
+ .optString("username", null));
+
+ // if we reached this point, it means that no error occurred
+ assertNull(testCaseMessage("Testcase defines an error but no error occurred!", testCaseComment), error);
+ } catch (XMLRPCUtils.XMLRPCUtilsException hce) {
+ assertNotNull(testCaseMessage("Error occurred but testcase does not define an error!", testCaseComment),
+ error);
+
+ assertEquals(testCaseMessage("Error message does not match the defined one!", testCaseComment), error
+ .getString("message"), getInstrumentation().getTargetContext().getString(hce.errorMsgId));
+ }
+
+ assertEquals(testCaseMessage("XMLRPC URL does not match the defined one!", testCaseComment), outputUrl,
+ xmlrpcUrl);
+
+ server.shutdown();
+ }
+
+ private MockWebServer setupMockHttpServer(MockWebServer server, JSONObject requestResponsesJson) throws
+ JSONException, IOException {
+ final Map mockRequestResponses = new HashMap<>();
+
+ final JSONArray serverMock = requestResponsesJson.getJSONArray("serverMock");
+ for (int i = 0; i < serverMock.length(); i++) {
+ final JSONObject reqRespJson = serverMock.getJSONObject(i);
+
+ final JSONObject reqJson = reqRespJson.getJSONObject("request");
+ Headers reqHeaders = json2Headers(reqJson.optJSONObject("headers"));
+
+ RecordedRequest recordedRequest = new RecordedRequest(reqJson.getString("method") + " " + reqJson
+ .getString("path") + " HTTP/1.1", reqHeaders, null, 0, null, 0, null);
+
+ final JSONObject respJson = reqRespJson.getJSONObject("response");
+ Headers respHeaders = json2Headers(respJson.optJSONObject("headers"));
+
+ String body = respJson.optString("body");
+ if (body.startsWith(sServerResponsesMagicScheme)) {
+ body = stringFromAsset(getInstrumentation().getContext(), sAssetPathBase + body.substring
+ (sServerResponsesMagicScheme.length()));
+ }
+
+ body = replaceServerMagicName(server, body);
+
+ final MockResponse resp = new MockResponse()
+ .setResponseCode(respJson.getInt("statusCode"))
+ .setHeaders(respHeaders)
+ .setBody(body);
+
+ mockRequestResponses.put(recordedRequest, resp);
+ }
+
+ server.setDispatcher(new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
+
+ for (Map.Entry reqResp : mockRequestResponses.entrySet()) {
+ final RecordedRequest mockRequest = reqResp.getKey();
+ if (mockRequest.getRequestLine().equals(request.getRequestLine())) {
+ return reqResp.getValue();
+ }
+ }
+ return new MockResponse().setResponseCode(404).setBody("");
+ }
+ });
+
+ return server;
+ }
+
+ private String replaceServerMagicName(MockWebServer server, String str) {
+ return str.replaceAll(sServerAddressMagicString, server.getHostName() + ":" + server.getPort());
+
+ }
+
+ private Headers json2Headers(JSONObject headersJson) throws JSONException {
+ if (headersJson != null) {
+ Headers.Builder headBuilder = new Headers.Builder();
+ Iterator headerKeys = headersJson.keys();
+ while (headerKeys.hasNext()) {
+ final String headerName = headerKeys.next();
+ headBuilder.add(headerName, headersJson.getString(headerName));
+ }
+
+ return headBuilder.build();
+ }
+
+ return new Headers.Builder().build();
+ }
+
+ private String testCaseMessage(String message, String testCaseComment) {
+ return message + " (on testCase: '" + testCaseComment + "')";
+ }
+}
diff --git a/WordPress/src/future/res/PluginsCheckerWPOrg.java b/WordPress/src/future/res/PluginsCheckerWPOrg.java
new file mode 100644
index 000000000000..ade442b88a63
--- /dev/null
+++ b/WordPress/src/future/res/PluginsCheckerWPOrg.java
@@ -0,0 +1,202 @@
+package org.wordpress.android.ui.accounts.helpers;
+
+import android.text.TextUtils;
+import android.webkit.URLUtil;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.util.AppLog;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * This class can test a WordPress installation for plugins/themes that cause problems with WordPress for Android connecting correctly.
+ *
+ * The tool is a black box scanner, it allows remote testing of a WordPress installation.
+ * Find problematic plugins and themes, configuration issues and other glitches that can cause problems with our apps.
+ *
+ */
+public class PluginsCheckerWPOrg {
+ private final static String BB_PLUGINS_LIST_URL = "https://raw.githubusercontent.com/wordpress-mobile/app-blocking-plugins/master/xmlrpc-plugins.json";
+
+ // Do not use the WP-APP user agent. Requests could be blocked if made from our app UA.
+ private final static String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36";
+
+ /** Socket timeout in milliseconds for the requests */
+ private static final int REQUEST_TIMEOUT_MS = 30000;
+
+ String mOriginalURL;
+
+ public PluginsCheckerWPOrg(String url) {
+ mOriginalURL = url;
+ }
+
+ /**
+ * This routine does a black box scanning on the plugis folder of the remote host, and tries to find plugins that cause
+ * problems connecting to the host from one of our mobile apps.
+ */
+ public List checkForPlugins() {
+ String responseHTML = downloadPluginsList();
+ if (TextUtils.isEmpty(responseHTML)) {
+ AppLog.w(AppLog.T.NUX, "Without the list we can't check if the host has some BB plugins installed on it.");
+ return null;
+ }
+
+ JSONArray listOfPlugins;
+ try {
+ listOfPlugins = new JSONArray(responseHTML);
+ } catch (JSONException e) {
+ AppLog.e(AppLog.T.NUX, "Error while parsing the list of plugins returned from the server.", e);
+ return null;
+ }
+
+ // we have the list. Start the process of checking for plugins
+ String baseURL = getBaseURL(mOriginalURL);
+
+ if (!baseURL.contains("/plugins")) {
+ baseURL = baseURL + "/wp-content/plugins/";
+ }
+
+ AppLog.i(AppLog.T.NUX, "The calculated plugins URL is the following: " + baseURL);
+
+ if (!URLUtil.isValidUrl(baseURL)) {
+ AppLog.w(AppLog.T.NUX, "The calculated plugins URL isn't a valid URL. Returning now.");
+ return null;
+ }
+
+ int respCode = openConnection(baseURL);
+ if (respCode != HttpURLConnection.HTTP_OK && respCode != 401 && respCode != 403) {
+ AppLog.w(AppLog.T.NUX, "The request to plugins URL returned with an unexpected HTTP error code. Returning now.");
+ return null;
+ }
+
+ AppLog.i(AppLog.T.NUX, "Start checking the plugins list..");
+ ArrayList listOfBBPlugins = new ArrayList<>();
+ for (int i=0; i 0) {
+ url = url.substring(0, url.indexOf(prefix));
+ }
+
+ if (!URLUtil.isValidUrl(url)) {
+ throw new IllegalArgumentException("The new URL " + url + " is not valid!");
+ }
+
+ return url;
+ }
+
+ public class Plugin {
+ String name;
+ String url;
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/helpers/FetchBlogListWPOrg.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/helpers/FetchBlogListWPOrg.java
index 6e9ec246cc3e..11531bb4ce7a 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/helpers/FetchBlogListWPOrg.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/helpers/FetchBlogListWPOrg.java
@@ -1,43 +1,22 @@
package org.wordpress.android.ui.accounts.helpers;
-import android.os.AsyncTask;
-import android.webkit.URLUtil;
-
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.analytics.AnalyticsTracker.Stat;
-import org.wordpress.android.util.AppLog;
-import org.wordpress.android.util.AppLog.T;
-import org.wordpress.android.util.BlogUtils;
-import org.wordpress.android.util.CrashlyticsUtils;
-import org.wordpress.android.util.CrashlyticsUtils.ExceptionType;
-import org.wordpress.android.util.CrashlyticsUtils.ExtraKey;
-import org.wordpress.android.util.UrlUtils;
-import org.wordpress.android.util.WPUrlUtils;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlrpc.android.ApiHelper;
-import org.xmlrpc.android.ApiHelper.Method;
-import org.xmlrpc.android.XMLRPCClientInterface;
-import org.xmlrpc.android.XMLRPCException;
-import org.xmlrpc.android.XMLRPCFactory;
-import org.xmlrpc.android.XMLRPCFault;
-import java.io.IOException;
+import org.xmlrpc.android.XMLRPCUtils;
+import org.xmlrpc.android.XMLRPCUtils.XMLRPCUtilsException;
+
+import android.os.AsyncTask;
+
import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
public class FetchBlogListWPOrg extends FetchBlogListAbstract {
private String mSelfHostedUrl;
private String mHttpUsername;
private String mHttpPassword;
- private boolean mHttpAuthRequired;
- private boolean mErroneousSslCertificate;
- private int mErrorMsgId;
public FetchBlogListWPOrg(String username, String password, String selfHostedUrl) {
super(username, password);
@@ -49,246 +28,42 @@ public void setHttpCredentials(String username, String password) {
mHttpPassword = password;
}
- private void handleXmlRpcFault(XMLRPCFault xmlRpcFault) {
- AppLog.e(T.NUX, "XMLRPCFault received from XMLRPC call wp.getUsersBlogs", xmlRpcFault);
- switch (xmlRpcFault.getFaultCode()) {
- case 403:
- mErrorMsgId = org.wordpress.android.R.string.username_or_password_incorrect;
- break;
- case 404:
- mErrorMsgId = org.wordpress.android.R.string.xmlrpc_error;
- break;
- case 425:
- mErrorMsgId = org.wordpress.android.R.string.account_two_step_auth_enabled;
- break;
- default:
- mErrorMsgId = org.wordpress.android.R.string.no_site_error;
- break;
- }
- }
-
public void fetchBlogList(Callback callback) {
(new FetchBlogListTask(callback)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- private String getRsdUrl(String baseUrl) throws SSLHandshakeException {
- String rsdUrl;
- rsdUrl = ApiHelper.getRSDMetaTagHrefRegEx(baseUrl);
- if (rsdUrl == null) {
- rsdUrl = ApiHelper.getRSDMetaTagHref(baseUrl);
- }
- return rsdUrl;
- }
-
- private boolean isHTTPAuthErrorMessage(Exception e) {
- if (e != null && e.getMessage() != null && e.getMessage().contains("401")) {
- mHttpAuthRequired = true;
- return true;
- }
- return false;
- }
-
- private String getXmlrpcByUserEnteredPath(String baseUrl) {
- String xmlRpcUrl;
- if (!UrlUtils.isValidUrlAndHostNotNull(baseUrl)) {
- AppLog.e(T.NUX, "invalid URL: " + baseUrl);
- mErrorMsgId = org.wordpress.android.R.string.invalid_site_url_message;
- return null;
- }
- URI uri = URI.create(baseUrl);
- XMLRPCClientInterface client = XMLRPCFactory.instantiate(uri, mHttpUsername, mHttpPassword);
- try {
- client.call(Method.LIST_METHODS);
- xmlRpcUrl = baseUrl;
- return xmlRpcUrl;
- } catch (XMLRPCException e) {
- AppLog.i(T.NUX, "system.listMethods failed on: " + baseUrl);
- if (isHTTPAuthErrorMessage(e)) {
- return null;
- }
- } catch (SSLHandshakeException e) {
- if (!WPUrlUtils.isWordPressCom(baseUrl)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected.");
- return null;
- } catch (SSLPeerUnverifiedException e) {
- if (!WPUrlUtils.isWordPressCom(baseUrl)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLPeerUnverifiedException failed. Erroneous SSL certificate detected.");
- return null;
- } catch (IOException e) {
- AppLog.i(T.NUX, "system.listMethods failed on: " + baseUrl);
- if (isHTTPAuthErrorMessage(e)) {
- return null;
- }
- } catch (XmlPullParserException e) {
- AppLog.i(T.NUX, "system.listMethods failed on: " + baseUrl);
- if (isHTTPAuthErrorMessage(e)) {
- return null;
- }
- } catch (IllegalArgumentException e) {
- // TODO: Hopefully a temporary log - remove it if we find a pattern of failing URLs
- CrashlyticsUtils.setString(ExtraKey.ENTERED_URL, baseUrl);
- CrashlyticsUtils.logException(e, ExceptionType.SPECIFIC, T.NUX);
- mErrorMsgId = org.wordpress.android.R.string.invalid_site_url_message;
- return null;
- }
-
- // Guess the xmlrpc path
- String guessURL = baseUrl;
- if (guessURL.substring(guessURL.length() - 1, guessURL.length()).equals("/")) {
- guessURL = guessURL.substring(0, guessURL.length() - 1);
- }
- guessURL += "/xmlrpc.php";
- uri = URI.create(guessURL);
- client = XMLRPCFactory.instantiate(uri, mHttpUsername, mHttpPassword);
- try {
- client.call(Method.LIST_METHODS);
- xmlRpcUrl = guessURL;
- return xmlRpcUrl;
- } catch (XMLRPCException e) {
- AnalyticsTracker.track(Stat.LOGIN_FAILED_TO_GUESS_XMLRPC);
- AppLog.e(T.NUX, "system.listMethods failed on: " + guessURL, e);
- } catch (SSLHandshakeException e) {
- if (!WPUrlUtils.isWordPressCom(baseUrl)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected.");
- return null;
- } catch (SSLPeerUnverifiedException e) {
- if (!WPUrlUtils.isWordPressCom(baseUrl)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLPeerUnverifiedException failed. Erroneous SSL certificate detected.");
- return null;
- } catch (IOException e) {
- AnalyticsTracker.track(Stat.LOGIN_FAILED_TO_GUESS_XMLRPC);
- AppLog.e(T.NUX, "system.listMethods failed on: " + guessURL, e);
- } catch (XmlPullParserException e) {
- AnalyticsTracker.track(Stat.LOGIN_FAILED_TO_GUESS_XMLRPC);
- AppLog.e(T.NUX, "system.listMethods failed on: " + guessURL, e);
- }
-
- return null;
- }
-
- // Attempts to retrieve the xmlrpc url for a self-hosted site, in this order:
- // 1: Try to retrieve it by finding the ?rsd url in the site's header
- // 2: Take whatever URL the user entered to see if that returns a correct response
- // 3: Finally, just guess as to what the xmlrpc url should be
- private String getSelfHostedXmlrpcUrl(String url) {
- String xmlrpcUrl;
-
- // Convert IDN names to punycode if necessary
- url = UrlUtils.convertUrlToPunycodeIfNeeded(url);
-
- // Add http to the beginning of the URL if needed
- url = UrlUtils.addUrlSchemeIfNeeded(url, false);
-
- if (!URLUtil.isValidUrl(url)) {
- mErrorMsgId = org.wordpress.android.R.string.invalid_site_url_message;
- return null;
- }
-
- // Attempt to get the XMLRPC URL via RSD
- String rsdUrl;
- try {
- rsdUrl = UrlUtils.addUrlSchemeIfNeeded(getRsdUrl(url), false);
- } catch (SSLHandshakeException e) {
- if (!WPUrlUtils.isWordPressCom(url)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected.");
- return null;
- }
-
- try {
- if (rsdUrl != null) {
- xmlrpcUrl = UrlUtils.addUrlSchemeIfNeeded(ApiHelper.getXMLRPCUrl(rsdUrl), false);
- if (xmlrpcUrl == null) {
- xmlrpcUrl = UrlUtils.addUrlSchemeIfNeeded(rsdUrl.replace("?rsd", ""), false);
- }
- } else {
- xmlrpcUrl = UrlUtils.addUrlSchemeIfNeeded(getXmlrpcByUserEnteredPath(url), false);
- }
- } catch (SSLHandshakeException e) {
- if (!WPUrlUtils.isWordPressCom(url)) {
- mErroneousSslCertificate = true;
- }
- AppLog.w(T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected.");
- return null;
- }
-
- return xmlrpcUrl;
- }
-
public class FetchBlogListTask extends AsyncTask>> {
private final Callback mCallback;
+ private boolean mHttpAuthRequired;
+ private boolean mErroneousSslCertificate;
+ private int mErrorMsgId;
private String mClientResponse = "";
public FetchBlogListTask(Callback callback) {
mCallback = callback;
}
+ private void trackInvalidInsertedURL(String url){
+ Map properties = new HashMap<>();
+ properties.put("user_inserted_url", url);
+ AnalyticsTracker.track(Stat.LOGIN_INSERTED_INVALID_URL, properties);
+ }
+
@Override
protected List