org.codaco.networkCanvas.plugin.NetworkCanvasClient.java Source code

Java tutorial

Introduction

Here is the source code for org.codaco.networkCanvas.plugin.NetworkCanvasClient.java

Source

package org.codaco.networkCanvas.plugin;

import android.net.Uri;
import android.util.Base64;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginManager;
import org.apache.cordova.file.FileUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

public class NetworkCanvasClient extends CordovaPlugin {
    private final String LogTag = "NetworkCanvas:ClientPlugin";
    private final Charset UTF_8 = Charset.forName("UTF-8");
    private final int BufferSize = 1024;

    private KeyStore keyStore;
    private FileUtils cdvFilePlugin;
    private CordovaResourceApi cdvResourceApi;

    class ApiRequest implements Runnable {
        private final String deviceId;
        private final URL url;
        private final String method;
        private final CallbackContext callbackContext;
        private Uri downloadDestination;
        private String body;

        ApiRequest(String deviceId, URL url, Uri downloadDestination, CallbackContext callbackContext) {
            this.deviceId = deviceId;
            this.url = url;
            this.downloadDestination = downloadDestination;
            this.method = "GET";
            this.callbackContext = callbackContext;
        }

        ApiRequest(String deviceId, URL url, String method, String body, CallbackContext callbackContext) {
            this.deviceId = deviceId;
            this.url = url;
            this.method = method;
            this.body = body;
            this.callbackContext = callbackContext;
        }

        /**
         * Adapted from https://developer.android.com/training/articles/security-ssl#java
         * Apache-2.0: https://developer.android.com/license
         */
        @Override
        public void run() {
            HttpsURLConnection urlConnection = null;
            InputStream respInputStream = null;
            InputStream respErrorStream = null;
            try {
                String algo = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algo);
                trustManagerFactory.init(keyStore);

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

                urlConnection = (HttpsURLConnection) url.openConnection();
                urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                urlConnection.setHostnameVerifier(localhostVerifier);

                urlConnection.setRequestProperty("Content-Type", "application/json");
                urlConnection.setRequestMethod(method);

                // Basic Auth
                String creds = deviceId + ":";
                String auth = Base64.encodeToString(creds.getBytes(UTF_8), Base64.DEFAULT);
                urlConnection.setRequestProperty("Authorization", "Basic " + auth);

                if (body != null) {
                    urlConnection.setDoOutput(true);
                    OutputStream out = urlConnection.getOutputStream();
                    out.write(body.getBytes(UTF_8));
                    out.close();
                }

                int code = urlConnection.getResponseCode();
                if (code >= HttpsURLConnection.HTTP_BAD_REQUEST) {
                    respErrorStream = urlConnection.getErrorStream();
                    JSONObject resp = inputStreamToJSONObject(respErrorStream);
                    respErrorStream.close();
                    callbackContext.error(resp);
                } else if (downloadDestination != null) {
                    // Save Server response to file and respond with file info
                    respInputStream = urlConnection.getInputStream();
                    inputStreamToFile(respInputStream, downloadDestination);
                    File savedFile = cdvResourceApi.mapUriToFile(downloadDestination);
                    JSONObject fileInfo = cdvFilePlugin.getEntryForFile(savedFile);
                    respInputStream.close();
                    callbackContext.success(fileInfo);
                } else {
                    // Respond directly with data
                    respInputStream = urlConnection.getInputStream();
                    JSONObject resp = inputStreamToJSONObject(respInputStream);
                    respInputStream.close();
                    callbackContext.success(resp);
                }
            } catch (NoSuchAlgorithmException e) {
                // Fatal. No device support for default algo.
                e.printStackTrace();
                callbackContext.error(jsonError("SSL not supported on this device."));
            } catch (IOException | JSONException | KeyStoreException | KeyManagementException e) {
                e.printStackTrace();
                callbackContext.error(jsonError(e));
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
            }

        }
    }

    private HostnameVerifier localhostVerifier = (hostname, session) -> {
        HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
        return verifier.verify("127.0.0.1", session);
    };

    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        cdvResourceApi = webView.getResourceApi();
        cdvFilePlugin = getCdvFilePlugin(webView);
    }

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        switch (action) {
        case "acceptCertificate":
            String cert = args.getString(0);
            this.acceptCertificate(cert, callbackContext);
            return true;
        case "download":
        case "send":
            String deviceId = args.getString(0);
            URL url = null;
            try {
                url = new URL(args.getString(1));
            } catch (MalformedURLException e) {
                e.printStackTrace();
                callbackContext.error(jsonError("Invalid URL"));
                return true;
            }

            ApiRequest req = null;
            String method = null;
            String body = null;
            if (action.equals("download")) {
                String destination = args.getString(2);
                Uri cordovaUri = Uri.parse(destination);
                Uri nativeUri = cdvResourceApi.remapUri(cordovaUri);
                req = new ApiRequest(deviceId, url, nativeUri, callbackContext);
            } else {
                method = args.getString(2);
                if (args.length() > 3) {
                    body = args.getString(3);
                }
                req = new ApiRequest(deviceId, url, method, body, callbackContext);
            }

            cordova.getThreadPool().execute(req);
            return true;
        }
        return false;
    }

    /**
     Supply a public Server certificate to be considered trusted.
     Async.
        
     Arguments to the cordova command:
     1. {string} The PEM-formatted certificate
        
     Adapted from https://developer.android.com/training/articles/security-ssl#java
     Apache-2.0: https://developer.android.com/license
     */
    private void acceptCertificate(String cert, CallbackContext callbackContext) {
        System.out.println(cert);
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            InputStream inputStream = new ByteArrayInputStream(cert.getBytes(UTF_8));
            Certificate certificate = factory.generateCertificate(inputStream);
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            keyStore.setCertificateEntry("NetworkCanvasServerCert", certificate);
            this.keyStore = keyStore;
            callbackContext.success();
        } catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
            callbackContext.error(e.getLocalizedMessage());
        }
    }

    //region response formatting
    private String inputStreamAsString(InputStream stream) throws IOException {
        char[] buffer = new char[BufferSize];
        try (InputStreamReader streamReader = new InputStreamReader(stream, UTF_8);
                BufferedReader bufferedReader = new BufferedReader(streamReader, BufferSize);
                StringWriter writer = new StringWriter();) {
            int charsRead = 0;
            while (-1 != (charsRead = bufferedReader.read(buffer))) {
                writer.write(buffer, 0, charsRead);
            }
            return writer.toString();
        }
    }

    private void ensureParentDir(Uri uri) {
        File file = new File(uri.getPath());
        File parentDir = file.getParentFile();
        if (parentDir != null) {
            parentDir.mkdirs();
        }
    }

    private void inputStreamToFile(InputStream inputStream, Uri destination) throws IOException {
        ensureParentDir(destination);
        byte[] buf = new byte[BufferSize];
        try (FileOutputStream outputStream = new FileOutputStream(destination.getPath())) {
            int bytesRead = 0;
            while (-1 != (bytesRead = inputStream.read(buf))) {
                outputStream.write(buf, 0, bytesRead);
            }
            outputStream.flush();
        }
    }

    private JSONObject inputStreamToJSONObject(InputStream stream) {
        String string = null;
        JSONObject obj = null;
        try {
            string = inputStreamAsString(stream);
        } catch (IOException e) {
            e.printStackTrace();
            return jsonError("Error reading Server response");
        }
        try {
            obj = new JSONObject(string);
        } catch (JSONException e) {
            e.printStackTrace();
            return jsonError("Error parsing Server response");
        }
        return obj;
    }

    /**
     * @param message User-friendly error message
     * @return JSON matching Server API format
     */
    private JSONObject jsonError(String message) {
        JSONObject err = new JSONObject();
        try {
            err.put("status", "error");
            err.put("message", message);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return err;
    }

    private JSONObject jsonError(Exception exception) {
        return jsonError(exception.getMessage());
    }
    //endregion

    private FileUtils getCdvFilePlugin(CordovaWebView webView) {
        PluginManager pluginManager = null;
        try {
            Method method = webView.getClass().getMethod("getPluginManager");
            pluginManager = (PluginManager) method.invoke(webView);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            // try getField
        }

        if (pluginManager == null) {
            try {
                Field field = webView.getClass().getField("pluginManager");
                pluginManager = (PluginManager) field.get(webView);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                // Log warning below
            }
        }

        if (pluginManager != null) {
            return (FileUtils) pluginManager.getPlugin("File");
        }

        LOG.w(LogTag, "Could not get FilePlugin; download interface will not be available");
        return null;
    }
}