diff --git a/build.gradle b/build.gradle index 1d29114c..aff8ff0c 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,6 @@ dependencies { compile 'org.mindrot:jbcrypt:0.3m' testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.1.2' - androidTestCompile 'com.squareup.okhttp:mockwebserver:2.4.0' } project.archivesBaseName = 'syncthing' diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java index acfa1cec..78a01ab2 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockRestApi.java @@ -6,12 +6,13 @@ import android.content.Context; import android.support.annotation.NonNull; import com.nutomic.syncthingandroid.syncthing.RestApi; +import java.net.URL; import java.util.ArrayList; import java.util.List; public class MockRestApi extends RestApi { - public MockRestApi(Context context, String url, String apiKey, + public MockRestApi(Context context, URL url, String apiKey, OnApiAvailableListener listener) { super(context, url, apiKey, listener, null); } @@ -76,7 +77,7 @@ public class MockRestApi extends RestApi { } @Override - public boolean editFolder(Folder folder, boolean create, Activity activity) { + public void editFolder(Folder folder, boolean create, Activity activity) { throw new UnsupportedOperationException(); } diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockSyncthingService.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockSyncthingService.java index 329d9144..264dd8b3 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/MockSyncthingService.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/MockSyncthingService.java @@ -6,6 +6,7 @@ import android.os.IBinder; import com.nutomic.syncthingandroid.syncthing.RestApi; import com.nutomic.syncthingandroid.syncthing.SyncthingService; +import java.net.URL; import java.util.LinkedList; public class MockSyncthingService extends SyncthingService { @@ -67,7 +68,7 @@ public class MockSyncthingService extends SyncthingService { } @Override - public String getWebGuiUrl() { + public URL getWebGuiUrl() { throw new UnsupportedOperationException(); } diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/GetTaskTest.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/GetTaskTest.java deleted file mode 100644 index b47133dd..00000000 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/GetTaskTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.nutomic.syncthingandroid.test.syncthing; - -import android.net.Uri; -import android.test.AndroidTestCase; - -import com.nutomic.syncthingandroid.syncthing.GetTask; -import com.nutomic.syncthingandroid.syncthing.RestApi; -import com.squareup.okhttp.mockwebserver.MockResponse; -import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; - -import java.io.IOException; - -public class GetTaskTest extends AndroidTestCase { - - private MockWebServer mServer; - - private static final String RESPONSE = "the response"; - - private static final String API_KEY = "the key"; - - private static final String PARAM_KEY_ONE = "first-param"; - - private static final String PARAM_VALUE_ONE = "first param value"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mServer = new MockWebServer(); - mServer.enqueue(new MockResponse().setBody(RESPONSE)); - mServer.play(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - - // TODO: causes problems, see https://github.com/square/okhttp/issues/1033 - //mServer.shutdown(); - } - - public void testGetNoParams() throws IOException, InterruptedException { - new GetTask("") { - @Override - protected void onPostExecute(String s) { - assertEquals(RESPONSE, s); - } - }.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY); - RecordedRequest request = mServer.takeRequest(); - assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY)); - Uri uri = Uri.parse(request.getPath()); - assertEquals(GetTask.URI_CONFIG, uri.getPath()); - } - - public void testGetParams() throws IOException, InterruptedException { - new GetTask("") { - @Override - protected void onPostExecute(String s) { - assertEquals(RESPONSE, s); - } - }.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY, PARAM_KEY_ONE, - PARAM_VALUE_ONE); - RecordedRequest request = mServer.takeRequest(); - assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY)); - Uri uri = Uri.parse(request.getPath()); - assertEquals(GetTask.URI_CONFIG, uri.getPath()); - assertEquals(PARAM_VALUE_ONE, uri.getQueryParameter(PARAM_KEY_ONE)); - } - -} diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/PollWebGuiAvailableTaskTest.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/PollWebGuiAvailableTaskTest.java deleted file mode 100644 index 2d77e43f..00000000 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/PollWebGuiAvailableTaskTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.nutomic.syncthingandroid.test.syncthing; - -import android.test.AndroidTestCase; - -import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask; -import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable; -import com.nutomic.syncthingandroid.syncthing.SyncthingService; -import com.nutomic.syncthingandroid.test.MockContext; -import com.nutomic.syncthingandroid.util.ConfigXml; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class PollWebGuiAvailableTaskTest extends AndroidTestCase { - - private ConfigXml mConfig; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mConfig = new ConfigXml(new MockContext(getContext())); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - - ConfigXml.getConfigFile(new MockContext(getContext())).delete(); - } - - public void testPolling() throws InterruptedException { - new SyncthingRunnable(new MockContext(getContext()), SyncthingRunnable.Command.main); - - String httpsCertPath = getContext().getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE; - - final CountDownLatch latch = new CountDownLatch(1); - new PollWebGuiAvailableTask(httpsCertPath) { - @Override - protected void onPostExecute(Void aVoid) { - latch.countDown(); - } - }.execute(mConfig.getWebGuiUrl()); - latch.await(1, TimeUnit.SECONDS); - - // TODO: Unit tests fail when Syncthing is killed SyncthingRunnable.killSyncthing(); - } -} diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/RestApiTest.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/RestApiTest.java index 25a4debf..0a7f6db6 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/RestApiTest.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/syncthing/RestApiTest.java @@ -2,7 +2,7 @@ package com.nutomic.syncthingandroid.test.syncthing; import android.test.AndroidTestCase; -import com.nutomic.syncthingandroid.syncthing.PollWebGuiAvailableTask; +import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask; import com.nutomic.syncthingandroid.syncthing.RestApi; import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable; import com.nutomic.syncthingandroid.syncthing.SyncthingService; @@ -28,13 +28,13 @@ public class RestApiTest extends AndroidTestCase { String httpsCertPath = getContext().getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE; final CountDownLatch latch = new CountDownLatch(2); - new PollWebGuiAvailableTask(httpsCertPath) { + new PollWebGuiAvailableTask(config.getWebGuiUrl(), httpsCertPath, config.getApiKey()) { @Override protected void onPostExecute(Void aVoid) { mApi.onWebGuiAvailable(); latch.countDown(); } - }.execute(config.getWebGuiUrl()); + }.execute(); mApi = new RestApi(getContext(), config.getWebGuiUrl(), config.getApiKey(), latch::countDown, null); latch.await(1, TimeUnit.SECONDS); diff --git a/src/androidTest/java/com/nutomic/syncthingandroid/test/util/ConfigXmlTest.java b/src/androidTest/java/com/nutomic/syncthingandroid/test/util/ConfigXmlTest.java index 7eb20502..bcef1852 100644 --- a/src/androidTest/java/com/nutomic/syncthingandroid/test/util/ConfigXmlTest.java +++ b/src/androidTest/java/com/nutomic/syncthingandroid/test/util/ConfigXmlTest.java @@ -29,7 +29,7 @@ public class ConfigXmlTest extends AndroidTestCase { } public void testGetWebGuiUrl() { - assertTrue(mConfig.getWebGuiUrl().startsWith("https://127.0.0.1:")); + assertTrue(mConfig.getWebGuiUrl().toString().startsWith("https://127.0.0.1:")); } } diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java index a1547426..478831d7 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java @@ -129,7 +129,7 @@ public class WebGuiActivity extends SyncthingActivity @Override public void onWebGuiAvailable() { - mWebView.loadUrl(getService().getWebGuiUrl()); + mWebView.loadUrl(getService().getWebGuiUrl().toString()); } /** diff --git a/src/main/java/com/nutomic/syncthingandroid/http/GetTask.java b/src/main/java/com/nutomic/syncthingandroid/http/GetTask.java new file mode 100644 index 00000000..b21048f3 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/http/GetTask.java @@ -0,0 +1,57 @@ +package com.nutomic.syncthingandroid.http; + +import android.util.Log; + +import javax.net.ssl.HttpsURLConnection; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +/** + * Performs a GET request to the Syncthing API + * Performs a GET request with no parameters to the URL in uri[0] with the path in uri[1] and + * returns the result as a String. + */ +public class GetTask extends RestTask { + + private static final String TAG = "GetTask"; + + public static final String URI_CONFIG = "/rest/system/config"; + public static final String URI_VERSION = "/rest/system/version"; + public static final String URI_SYSTEM = "/rest/system/status"; + public static final String URI_CONNECTIONS = "/rest/system/connections"; + public static final String URI_MODEL = "/rest/db/status"; + public static final String URI_DEVICEID = "/rest/svc/deviceid"; + public static final String URI_REPORT = "/rest/svc/report"; + public static final String URI_EVENTS = "/rest/events"; + + public GetTask(URL url, String path, String httpsCertPath, String apiKey) { + super(url, path, httpsCertPath, apiKey); + } + + /** + * @param params Keys and values for the query string. + */ + @Override + protected String doInBackground(String... params) { + try { + HttpsURLConnection connection = openConnection(params); + Log.v(TAG, "Calling Rest API at " + connection.getURL()); + connection.connect(); + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + String line; + String result = ""; + while ((line = br.readLine()) != null) { + result += line; + } + br.close(); + Log.v(TAG, "API call result: " + result); + return result; + } catch (IOException e) { + Log.w(TAG, "Failed to call rest api", e); + return null; + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/http/PollWebGuiAvailableTask.java b/src/main/java/com/nutomic/syncthingandroid/http/PollWebGuiAvailableTask.java new file mode 100644 index 00000000..22b46f19 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/http/PollWebGuiAvailableTask.java @@ -0,0 +1,47 @@ +package com.nutomic.syncthingandroid.http; + + +import android.util.Log; + +import javax.net.ssl.HttpsURLConnection; +import java.io.IOException; +import java.net.URL; + +/** + * Polls to load the web interface, until we receive http status 200. + */ +public abstract class PollWebGuiAvailableTask extends RestTask { + + private static final String TAG = "PollWebGuiAvailableTask"; + + /** + * Interval in ms, at which connections to the web gui are performed on first start + * to find out if it's online. + */ + private static final long WEB_GUI_POLL_INTERVAL = 100; + + public PollWebGuiAvailableTask(URL url, String httpsCertPath, String apiKey) { + super(url, "", httpsCertPath, apiKey); + } + + @Override + protected Void doInBackground(Void... aVoid) { + int status = 0; + do { + try { + HttpsURLConnection connection = openConnection(); + connection.connect(); + status = connection.getResponseCode(); + } catch (IOException e) { + // We catch this in every call, as long as the service is not online, so we ignore and continue. + try { + Thread.sleep(WEB_GUI_POLL_INTERVAL); + } catch (InterruptedException e2) { + Log.w(TAG, "Failed to sleep", e2); + } + } + } while (status != HttpsURLConnection.HTTP_OK); + return null; + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/http/PostTask.java b/src/main/java/com/nutomic/syncthingandroid/http/PostTask.java new file mode 100644 index 00000000..9b7ef453 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/http/PostTask.java @@ -0,0 +1,60 @@ +package com.nutomic.syncthingandroid.http; + +import android.util.Log; + +import javax.net.ssl.HttpsURLConnection; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URL; + +/** + * Sends a POST request to the Syncthing API. + */ +public class PostTask extends RestTask { + + private static final String TAG = "PostTask"; + + public static final String URI_CONFIG = "/rest/system/config"; + public static final String URI_SCAN = "/rest/db/scan"; + + public PostTask(URL url, String path, String httpsCertPath, String apiKey) { + super(url, path, httpsCertPath, apiKey); + } + + /** + * For {@link #URI_CONFIG}, params[0] must contain the config. + * + * For {@link #URI_SCAN}, params[0] must contain the folder, and params[1] the subfolder. + */ + @Override + protected Boolean doInBackground(String... params) { + try { + HttpsURLConnection connection = (mPath.equals(URI_SCAN)) + ? openConnection("folder", params[0], "sub", params[1]) + : openConnection(); + connection.setRequestMethod("POST"); + Log.v(TAG, "Calling Rest API at " + connection.getURL()); + + if (mPath.equals(URI_CONFIG)) { + OutputStream os = connection.getOutputStream(); + os.write(params[0].getBytes("UTF-8")); + } + connection.connect(); + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + String line; + String result = ""; + while ((line = br.readLine()) != null) { + result += line; + } + br.close(); + Log.v(TAG, "API call result: " + result); + return true; + } catch (IOException e) { + Log.w(TAG, "Failed to call rest api", e); + return false; + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/http/RestTask.java b/src/main/java/com/nutomic/syncthingandroid/http/RestTask.java new file mode 100644 index 00000000..d36e1b4e --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/http/RestTask.java @@ -0,0 +1,111 @@ +package com.nutomic.syncthingandroid.http; + + +import android.annotation.SuppressLint; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; +import com.nutomic.syncthingandroid.syncthing.RestApi; + +import javax.net.ssl.*; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +public abstract class RestTask extends AsyncTask { + + private static final String TAG = "RestTask"; + + private final URL mUrl; + protected final String mPath; + private final String mHttpsCertPath; + private final String mApiKey; + + public RestTask(URL url, String path, String httpsCertPath, String apiKey) { + mUrl = url; + mPath = path; + mHttpsCertPath = httpsCertPath; + mApiKey = apiKey; + } + + protected HttpsURLConnection openConnection(String... params) throws IOException { + Uri.Builder uriBuilder = Uri.parse(mUrl.toString()) + .buildUpon() + .path(mPath); + for (int paramCounter = 0; paramCounter + 1 < params.length; ) { + uriBuilder.appendQueryParameter(params[paramCounter++], params[paramCounter++]); + } + URL url = new URL(uriBuilder.build().toString()); + + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setRequestProperty(RestApi.HEADER_API_KEY, mApiKey); + connection.setHostnameVerifier((h, s) -> true); + connection.setSSLSocketFactory(getSslSocketFactory()); + return connection; + } + + private SSLSocketFactory getSslSocketFactory() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new SyncthingTrustManager()}, + new SecureRandom()); + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + Log.w(TAG, e); + return null; + } + } + + /* + * TrustManager checking against the local Syncthing instance's https public key. + * + * Based on http://stackoverflow.com/questions/16719959#16759793 + */ + private class SyncthingTrustManager implements X509TrustManager { + + private static final String TAG = "SyncthingTrustManager"; + + @Override + @SuppressLint("TrustAllX509TrustManager") + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + + /** + * Verifies certs against public key of the local syncthing instance + */ + @Override + public void checkServerTrusted(X509Certificate[] certs, + String authType) throws CertificateException { + InputStream is = null; + try { + is = new FileInputStream(mHttpsCertPath); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate ca = (X509Certificate) cf.generateCertificate(is); + for (X509Certificate cert : certs) { + cert.verify(ca.getPublicKey()); + } + } catch (FileNotFoundException | NoSuchAlgorithmException | InvalidKeyException | + NoSuchProviderException | SignatureException e) { + throw new CertificateException("Untrusted Certificate!", e); + } finally { + try { + if (is != null) + is.close(); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java deleted file mode 100644 index 74bc6eb9..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.nutomic.syncthingandroid.syncthing; - -import android.os.AsyncTask; -import android.util.Log; - -import com.nutomic.syncthingandroid.util.Https; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.LinkedList; - -/** - * Performs a GET request with no parameters to the URL in uri[0] with the path in uri[1] and - * returns the result as a String. - */ -public class GetTask extends AsyncTask { - - private static final String TAG = "GetTask"; - - public static final String URI_CONFIG = "/rest/system/config"; - public static final String URI_VERSION = "/rest/system/version"; - public static final String URI_SYSTEM = "/rest/system/status"; - public static final String URI_CONNECTIONS = "/rest/system/connections"; - public static final String URI_MODEL = "/rest/db/status"; - public static final String URI_DEVICEID = "/rest/svc/deviceid"; - public static final String URI_REPORT = "/rest/svc/report"; - public static final String URI_EVENTS = "/rest/events"; - - private final String mHttpsCertPath; - - public GetTask(String httpsCertPath) { - mHttpsCertPath = httpsCertPath; - } - - /** - * params[0] Syncthing hostname - * params[1] URI to call - * params[2] Syncthing API key - * params[3] optional parameter key - * params[4] optional parameter value - */ - @Override - protected String doInBackground(String... params) { - String fullUri = params[0] + params[1]; - Log.v(TAG, "Calling Rest API at " + fullUri); - - if (params.length >= 5) { - LinkedList urlParams = new LinkedList<>(); - for (int paramCounter = 3; paramCounter + 1 < params.length; ) { - urlParams.add(new BasicNameValuePair(params[paramCounter++], params[paramCounter++])); - } - fullUri += "?" + URLEncodedUtils.format(urlParams, HTTP.UTF_8); - } - - // Retry at most 5 times before failing - for (int i = 0; i < 5; i++) { - HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath); - HttpGet get = new HttpGet(fullUri); - get.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[2])); - - if (isCancelled()) - return null; - - try { - HttpResponse response = httpclient.execute(get); - HttpEntity entity = response.getEntity(); - - if (entity != null) { - InputStream is = entity.getContent(); - - BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); - String line; - String result = ""; - while ((line = br.readLine()) != null) { - result += line; - } - br.close(); - Log.v(TAG, "API call result: " + result); - return result; - } - } catch (IOException|IllegalArgumentException e) { - Log.w(TAG, "Failed to call Rest API at " + fullUri); - } - try { - // Don't push the API too hard - Thread.sleep(500 * i); - } catch (InterruptedException e) { - Log.w(TAG, e); - } - Log.w(TAG, "Retrying GetTask Rest API call (" + (i + 1) + "/5)"); - } - return null; - } - -} diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/PollWebGuiAvailableTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/PollWebGuiAvailableTask.java deleted file mode 100644 index e178ed03..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/PollWebGuiAvailableTask.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.nutomic.syncthingandroid.syncthing; - - -import android.os.AsyncTask; - -import com.nutomic.syncthingandroid.util.Https; - -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.conn.HttpHostConnectException; - -import java.io.IOException; - -/** - * Polls SYNCTHING_URL until it returns HTTP status OK, then calls all listeners - * in mOnWebGuiAvailableListeners and clears it. - */ -public abstract class PollWebGuiAvailableTask extends AsyncTask { - - private static final String TAG = "PollWebGuiAvailableTask"; - - /** - * Interval in ms, at which connections to the web gui are performed on first start - * to find out if it's online. - */ - private static final long WEB_GUI_POLL_INTERVAL = 100; - - private final String mHttpsCertPath; - - public PollWebGuiAvailableTask(String httpsCertPath) { - mHttpsCertPath = httpsCertPath; - } - - /** - * @param url The URL of the web GUI (eg 127.0.0.1:8384). - */ - @Override - protected Void doInBackground(String... url) { - int status = 0; - HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath); - HttpHead head = new HttpHead(url[0]); - do { - try { - Thread.sleep(WEB_GUI_POLL_INTERVAL); - HttpResponse response = httpclient.execute(head); - // NOTE: status is not really needed, as HttpHostConnectException is thrown - // earlier. - status = response.getStatusLine().getStatusCode(); - } catch (HttpHostConnectException e) { - // We catch this in every call, as long as the service is not online, - // so we ignore and continue. - } catch (IOException|InterruptedException|IllegalArgumentException e) { - //Log.w(TAG, "Failed to poll for web interface", e); - } - } while (status != HttpStatus.SC_OK && status != HttpStatus.SC_UNAUTHORIZED); - return null; - } - -} diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostConfigTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostConfigTask.java deleted file mode 100644 index c3719081..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostConfigTask.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.nutomic.syncthingandroid.syncthing; - -import android.os.AsyncTask; -import android.util.Log; - -import com.nutomic.syncthingandroid.util.Https; - -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; -import org.apache.http.protocol.HTTP; - -import java.io.IOException; - -/** - * Sends a new config to {@link #URI_CONFIG}. - */ -public class PostConfigTask extends AsyncTask { - - private static final String TAG = "PostConfigTask"; - - public static final String URI_CONFIG = "/rest/system/config"; - - private final String mHttpsCertPath; - - public PostConfigTask(String httpsCertPath) { - mHttpsCertPath = httpsCertPath; - } - - /** - * params[0] Syncthing hostname - * params[1] Syncthing API key - * params[2] The new config - */ - @Override - protected Boolean doInBackground(String... params) { - String fullUri = params[0] + URI_CONFIG; - Log.v(TAG, "Calling Rest API at " + fullUri); - - HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath); - HttpPost post = new HttpPost(fullUri); - post.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[1])); - - try { - post.setEntity(new StringEntity(params[2], HTTP.UTF_8)); - Log.v(TAG, "API call parameters: " + params[2]); - httpclient.execute(post); - } catch (IOException|IllegalArgumentException e) { - Log.w(TAG, "Failed to call Rest API at " + fullUri, e); - return false; - } - return true; - } - -} diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostScanTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostScanTask.java deleted file mode 100644 index 202b2302..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostScanTask.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.nutomic.syncthingandroid.syncthing; - -import android.os.AsyncTask; -import android.util.Log; - -import com.nutomic.syncthingandroid.util.Https; - -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; - -import java.io.IOException; -import java.util.LinkedList; - -/** - * Performs a POST request to {@link #URI_SCAN} to notify Syncthing of a changed file or folder. - */ -public class PostScanTask extends AsyncTask { - - private static final String TAG = "PostScanTask"; - - public static final String URI_SCAN = "/rest/db/scan"; - - private final String mHttpsCertPath; - - public PostScanTask(String httpsCertPath) { - mHttpsCertPath = httpsCertPath; - } - - /** - * params[0] Syncthing hostname - * params[1] Syncthing API key - * params[2] folder parameter (the Syncthing folder to update) - * params[3] sub parameter (the subfolder to update - */ - @Override - protected Void doInBackground(String... params) { - String fullUri = params[0] + URI_SCAN; - - LinkedList urlParams = new LinkedList<>(); - urlParams.add(new BasicNameValuePair("folder", params[2])); - urlParams.add(new BasicNameValuePair("sub", params[3])); - fullUri += "?" + URLEncodedUtils.format(urlParams, HTTP.UTF_8); - Log.v(TAG, "Calling Rest API at " + fullUri); - - // Retry at most 5 times before failing - for (int i = 0; i < 5; i++) { - HttpClient httpclient = Https.createHttpsClient(mHttpsCertPath); - HttpPost post = new HttpPost(fullUri); - post.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[1])); - - if (isCancelled()) - return null; - - try { - HttpResponse response = httpclient.execute(post); - if (response.getEntity() != null) - return null; - } catch (IOException | IllegalArgumentException e) { - Log.w(TAG, "Failed to call Rest API at " + fullUri); - } - try { - // Don't push the API too hard - Thread.sleep(500 * i); - } catch (InterruptedException e) { - Log.w(TAG, e); - } - Log.w(TAG, "Retrying GetTask Rest API call (" + (i + 1) + "/5)"); - } - return null; - } - -} diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java index 0895a5e9..db6b7864 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java @@ -11,26 +11,23 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; - import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.SerializedName; import com.nutomic.syncthingandroid.BuildConfig; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.activities.RestartActivity; +import com.nutomic.syncthingandroid.http.GetTask; +import com.nutomic.syncthingandroid.http.PostTask; import com.nutomic.syncthingandroid.util.FolderObserver; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.Serializable; +import java.net.URL; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** @@ -169,7 +166,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, private String mVersion; - private final String mUrl; + private final URL mUrl; private final String mApiKey; @@ -203,7 +200,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, */ private final Map mCacheFolderPathLookup = new HashMap<>(); - public RestApi(Context context, String url, String apiKey, OnApiAvailableListener apiListener, + public RestApi(Context context, URL url, String apiKey, OnApiAvailableListener apiListener, OnConfigChangedListener configListener) { mContext = context; mUrl = url; @@ -237,7 +234,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, @Override public void onWebGuiAvailable() { mAvailableCount.set(0); - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_VERSION, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { if (s == null) @@ -253,8 +250,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to parse config", e); } } - }.execute(mUrl, GetTask.URI_VERSION, mApiKey); - new GetTask(mHttpsCertPath) { + }.execute(); + new GetTask(mUrl, GetTask.URI_CONFIG, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String config) { try { @@ -264,7 +261,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to parse config", e); } } - }.execute(mUrl, GetTask.URI_CONFIG, mApiKey); + }.execute(); getSystemInfo(info -> { mLocalDeviceId = info.myID; tryIsAvailable(); @@ -367,7 +364,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, */ public void requireRestart(Activity activity) { if (mRestartPostponed) { - new PostConfigTask(mHttpsCertPath).execute(mUrl, mApiKey, mConfig.toString()); + new PostTask(mUrl, PostTask.URI_CONFIG, mHttpsCertPath, mApiKey).execute(mConfig.toString()); } else { activity.startActivity(new Intent(mContext, RestartActivity.class)); } @@ -380,13 +377,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * This executes a restart immediately, and does not show a dialog. */ public void updateConfig() { - new PostConfigTask(mHttpsCertPath) { + new PostTask(mUrl, PostTask.URI_CONFIG, mHttpsCertPath, mApiKey) { @Override - protected void onPostExecute(Boolean aBoolean) { + protected void onPostExecute(Boolean b) { mContext.startService(new Intent(mContext, SyncthingService.class) .setAction(SyncthingService.ACTION_RESTART)); } - }.execute(mUrl, mApiKey, mConfig.toString()); + }.execute(mConfig.toString()); } @@ -445,7 +442,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * @param listener Callback invoked when the result is received. */ public void getSystemInfo(final OnReceiveSystemInfoListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_SYSTEM, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { if (s == null) @@ -472,7 +469,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to read system info", e); } } - }.execute(mUrl, GetTask.URI_SYSTEM, mApiKey); + }.execute(); } /** @@ -481,7 +478,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * @param listener Callback invoked when the result is received. */ public void getSystemVersion(final OnReceiveSystemVersionListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_VERSION, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String response) { if (response == null) { @@ -495,7 +492,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to read system info", e); } } - }.execute(mUrl, GetTask.URI_VERSION, mApiKey); + }.execute(); } /** @@ -594,7 +591,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * Use the key {@link #TOTAL_STATS} to get connection info for the local device. */ public void getConnections(final OnReceiveConnectionsListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_CONNECTIONS, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { if (s == null) @@ -650,7 +647,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to parse connections", e); } } - }.execute(mUrl, GetTask.URI_CONNECTIONS, mApiKey); + }.execute(); } /** @@ -720,7 +717,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * Returns status information about the folder with the given id. */ public void getModel(final String folderId, final OnReceiveModelListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_MODEL, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { if (s == null) @@ -748,7 +745,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to read folder info", e); } } - }.execute(mUrl, GetTask.URI_MODEL, mApiKey, "folder", folderId); + }.execute("folder", folderId); } /** @@ -779,7 +776,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * The OnReceiveEventListeners onEvent method is called for each event. */ public final void getEvents(final long sinceId, final long limit, final OnReceiveEventListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_EVENTS, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { if (s == null) @@ -815,7 +812,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, Log.w(TAG, "Failed to read events", e); } } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, GetTask.URI_EVENTS, mApiKey, + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "since", String.valueOf(sinceId), "limit", String.valueOf(limit)); } @@ -998,7 +995,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * Normalizes a given device ID. */ public void normalizeDeviceId(final String id, final OnDeviceIdNormalizedListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_DEVICEID, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { super.onPostExecute(s); @@ -1016,7 +1013,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, } listener.onDeviceIdNormalized(normalized, error); } - }.execute(mUrl, GetTask.URI_DEVICEID, mApiKey, "id", id); + }.execute("id", id); } /** @@ -1050,7 +1047,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, */ @Override public void onFolderFileChange(String folderId, String relativePath) { - new PostScanTask(mHttpsCertPath).execute(mUrl, mApiKey, folderId, relativePath); + new PostTask(mUrl, PostTask.URI_SCAN, mHttpsCertPath, mApiKey).execute(folderId, relativePath); } /** @@ -1109,7 +1106,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, * Returns prettyfied usage report. */ public void getUsageReport(final OnReceiveUsageReportListener listener) { - new GetTask(mHttpsCertPath) { + new GetTask(mUrl, GetTask.URI_REPORT, mHttpsCertPath, mApiKey) { @Override protected void onPostExecute(String s) { try { @@ -1121,7 +1118,7 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, throw new RuntimeException("Failed to prettify usage report", e); } } - }.execute(mUrl, GetTask.URI_REPORT, mApiKey); + }.execute(); } /** diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java index 7b9b15b7..598a2ff3 100644 --- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java +++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java @@ -22,6 +22,7 @@ import android.widget.Toast; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.activities.MainActivity; +import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask; import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.FolderObserver; import com.nutomic.syncthingandroid.util.PRNGFixes; @@ -30,6 +31,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.net.URL; import java.nio.channels.FileChannel; import java.util.HashSet; import java.util.Iterator; @@ -226,8 +228,8 @@ public class SyncthingService extends Service implements registerOnWebGuiAvailableListener(mApi); if (mEventProcessor != null) registerOnWebGuiAvailableListener(mEventProcessor); - new PollWebGuiAvailableTaskImpl(getFilesDir() + "/" + HTTPS_CERT_FILE) - .execute(mConfig.getWebGuiUrl()); + new PollWebGuiAvailableTaskImpl(getWebGuiUrl(), getFilesDir() + "/" + HTTPS_CERT_FILE, mConfig.getApiKey()) + .execute(); mRunnable = new SyncthingRunnable(this, SyncthingRunnable.Command.main); new Thread(mRunnable).start(); updateNotification(); @@ -321,10 +323,10 @@ public class SyncthingService extends Service implements * version, and reads syncthing URL and API key (these are passed internally as * {@code Pair}. */ - private class StartupTask extends AsyncTask> { + private class StartupTask extends AsyncTask> { @Override - protected Pair doInBackground(Void... voids) { + protected Pair doInBackground(Void... voids) { try { mConfig = new ConfigXml(SyncthingService.this); return new Pair<>(mConfig.getWebGuiUrl(), mConfig.getApiKey()); @@ -334,7 +336,7 @@ public class SyncthingService extends Service implements } @Override - protected void onPostExecute(Pair urlAndKey) { + protected void onPostExecute(Pair urlAndKey) { if (urlAndKey == null) { Toast.makeText(SyncthingService.this, R.string.config_create_failed, Toast.LENGTH_LONG).show(); @@ -478,8 +480,8 @@ public class SyncthingService extends Service implements private class PollWebGuiAvailableTaskImpl extends PollWebGuiAvailableTask { - public PollWebGuiAvailableTaskImpl(String httpsCertPath) { - super(httpsCertPath); + public PollWebGuiAvailableTaskImpl(URL url, String httpsCertPath, String apiKey) { + super(url, httpsCertPath, apiKey); } /** @@ -528,7 +530,7 @@ public class SyncthingService extends Service implements } } - public String getWebGuiUrl() { + public URL getWebGuiUrl() { return mConfig.getWebGuiUrl(); } diff --git a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java index cc038ff3..e54c5db1 100644 --- a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java +++ b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java @@ -18,6 +18,8 @@ import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.security.SecureRandom; import java.util.Arrays; import java.util.Locale; @@ -96,8 +98,12 @@ public class ConfigXml { return new File(context.getFilesDir(), CONFIG_FILE); } - public String getWebGuiUrl() { - return "https://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent(); + public URL getWebGuiUrl() { + try { + return new URL("https://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent()); + } catch (MalformedURLException e) { + throw new RuntimeException("Failed to parse web interface URL", e); + } } public String getApiKey() { diff --git a/src/main/java/com/nutomic/syncthingandroid/util/CustomX509TrustManager.java b/src/main/java/com/nutomic/syncthingandroid/util/CustomX509TrustManager.java deleted file mode 100644 index 229fc76d..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/util/CustomX509TrustManager.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.nutomic.syncthingandroid.util; - -import android.annotation.SuppressLint; -import android.util.Log; - -import org.apache.http.conn.ssl.SSLSocketFactory; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.Socket; -import java.security.InvalidKeyException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; - - -/* - * TrustManager allowing the Syncthing https.pem CA - * - * Based on http://stackoverflow.com/questions/16719959#16759793 - * - */ -public class CustomX509TrustManager implements X509TrustManager { - - private static final String TAG = "CustomX509TrustManager"; - - /** - * Taken from: http://janis.peisenieks.lv/en/76/english-making-an-ssl-connection-via-android/ - * - */ - public static class CustomSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); - - public CustomSSLSocketFactory(SSLContext context) - throws KeyManagementException, NoSuchAlgorithmException, - KeyStoreException, UnrecoverableKeyException { - super(null); - sslContext = context; - } - - @Override - public Socket createSocket(Socket socket, String host, int port, - boolean autoClose) throws IOException { - return sslContext.getSocketFactory().createSocket(socket, host, port, - autoClose); - } - - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); - } - } - - private final String mHttpsCertPath; - - public CustomX509TrustManager(String httpsCertPath) { - mHttpsCertPath = httpsCertPath; - } - - @Override - @SuppressLint("TrustAllX509TrustManager") - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - } - - /** - * Verifies certs against public key of the local syncthing instance - */ - @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, - String authType) throws CertificateException { - InputStream inStream = null; - try { - inStream = new FileInputStream(mHttpsCertPath); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - X509Certificate ca = (X509Certificate) - cf.generateCertificate(inStream); - for (X509Certificate cert : certs) { - cert.verify(ca.getPublicKey()); - } - } catch (FileNotFoundException |NoSuchAlgorithmException|InvalidKeyException| - NoSuchProviderException |SignatureException e) { - throw new CertificateException("Untrusted Certificate!", e); - } finally { - try { - if (inStream != null) - inStream.close(); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - public X509Certificate[] getAcceptedIssuers() { - return null; - } -} diff --git a/src/main/java/com/nutomic/syncthingandroid/util/Https.java b/src/main/java/com/nutomic/syncthingandroid/util/Https.java deleted file mode 100644 index 17ec2d8c..00000000 --- a/src/main/java/com/nutomic/syncthingandroid/util/Https.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.nutomic.syncthingandroid.util; - -import android.util.Log; - -import org.apache.http.client.HttpClient; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; - -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -/* - * Wrapper for HTTPS Clients allowing the Syncthing https.pem CA - * - */ -public class Https { - - private static final String TAG = "HTTPS"; - - /** - * Create a HTTPClient that verifies a custom PEM certificate - * - * @param httpsCertPath refers to the filepath of a SSL/TLS PEM certificate. - */ - public static HttpClient createHttpsClient(String httpsCertPath) { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, new TrustManager[] { new CustomX509TrustManager(httpsCertPath) }, - new SecureRandom()); - HttpClient client = new DefaultHttpClient(); - SSLSocketFactory ssf = new CustomX509TrustManager.CustomSSLSocketFactory(ctx); - ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - ClientConnectionManager ccm = client.getConnectionManager(); - SchemeRegistry sr = ccm.getSchemeRegistry(); - sr.register(new Scheme("https", ssf, 443)); - return new DefaultHttpClient(ccm, - client.getParams()); - } catch (NoSuchAlgorithmException|KeyManagementException|KeyStoreException| - UnrecoverableKeyException e) { - Log.w(TAG, e); - } - return null; - } -}