From 57e92e8a567a3941e84754af1ce47694e594a5f6 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Tue, 22 Oct 2013 15:36:04 -0400 Subject: [PATCH] Update snapshot of FileTransfer.java to cordova-plugin-file-transfer@9d2246e85c7f4b002218e6d231c0d452a9650cf7 --- .../src/org/apache/cordova/FileTransfer.java | 317 ++++++++---------- 1 file changed, 132 insertions(+), 185 deletions(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 1b45fef2..76dae9e6 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -22,19 +22,14 @@ import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.security.cert.CertificateException; @@ -52,9 +47,10 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import org.apache.cordova.CordovaResourceApi.OpenForReadResult; import org.apache.cordova.api.CallbackContext; -import org.apache.cordova.api.CordovaPlugin; import org.apache.cordova.api.PluginResult; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -64,8 +60,6 @@ import android.os.Build; import android.util.Log; import android.webkit.CookieManager; -import com.squareup.okhttp.OkHttpClient; - public class FileTransfer extends CordovaPlugin { private static final String LOG_TAG = "FileTransfer"; @@ -81,8 +75,6 @@ public class FileTransfer extends CordovaPlugin { private static HashMap activeRequests = new HashMap(); private static final int MAX_BUFFER_SIZE = 16 * 1024; - private static OkHttpClient httpClient = new OkHttpClient(); - private static final class RequestContext { String source; String target; @@ -111,20 +103,20 @@ public class FileTransfer extends CordovaPlugin { * the HTTP Content-Length header value from the server. */ private static abstract class TrackingInputStream extends FilterInputStream { - public TrackingInputStream(final InputStream in) { - super(in); - } + public TrackingInputStream(final InputStream in) { + super(in); + } public abstract long getTotalRawBytesRead(); - } + } private static class ExposedGZIPInputStream extends GZIPInputStream { - public ExposedGZIPInputStream(final InputStream in) throws IOException { - super(in); - } - public Inflater getInflater() { - return inf; - } - } + public ExposedGZIPInputStream(final InputStream in) throws IOException { + super(in); + } + public Inflater getInflater() { + return inf; + } + } /** * Provides raw bytes-read tracking for a GZIP input stream. Reports the @@ -132,30 +124,30 @@ public class FileTransfer extends CordovaPlugin { * number of uncompressed bytes. */ private static class TrackingGZIPInputStream extends TrackingInputStream { - private ExposedGZIPInputStream gzin; - public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException { - super(gzin); - this.gzin = gzin; - } - public long getTotalRawBytesRead() { - return gzin.getInflater().getBytesRead(); - } - } + private ExposedGZIPInputStream gzin; + public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException { + super(gzin); + this.gzin = gzin; + } + public long getTotalRawBytesRead() { + return gzin.getInflater().getBytesRead(); + } + } /** * Provides simple total-bytes-read tracking for an existing InputStream */ - private static class TrackingHTTPInputStream extends TrackingInputStream { + private static class SimpleTrackingInputStream extends TrackingInputStream { private long bytesRead = 0; - public TrackingHTTPInputStream(InputStream stream) { + public SimpleTrackingInputStream(InputStream stream) { super(stream); } private int updateBytesRead(int newBytesRead) { - if (newBytesRead != -1) { - bytesRead += newBytesRead; - } - return newBytesRead; + if (newBytesRead != -1) { + bytesRead += newBytesRead; + } + return newBytesRead; } @Override @@ -174,7 +166,7 @@ public class FileTransfer extends CordovaPlugin { } public long getTotalRawBytesRead() { - return bytesRead; + return bytesRead; } } @@ -251,6 +243,8 @@ public class FileTransfer extends CordovaPlugin { final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8); final String objectId = args.getString(9); final String httpMethod = getArgument(args, 10, "POST"); + + final CordovaResourceApi resourceApi = webView.getResourceApi(); Log.d(LOG_TAG, "fileKey: " + fileKey); Log.d(LOG_TAG, "fileName: " + fileName); @@ -262,16 +256,20 @@ public class FileTransfer extends CordovaPlugin { Log.d(LOG_TAG, "objectId: " + objectId); Log.d(LOG_TAG, "httpMethod: " + httpMethod); - final URL url; - try { - url = new URL(target); - } catch (MalformedURLException e) { + final Uri targetUri = resourceApi.remapUri(Uri.parse(target)); + // Accept a path or a URI for the source. + Uri tmpSrc = Uri.parse(source); + final Uri sourceUri = resourceApi.remapUri( + tmpSrc.getScheme() != null ? tmpSrc : Uri.fromFile(new File(source))); + + int uriType = CordovaResourceApi.getUriType(targetUri); + final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS; + if (uriType != CordovaResourceApi.URI_TYPE_HTTP && !useHttps) { JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, error.toString(), e); + Log.e(LOG_TAG, "Unsupported URI: " + targetUri); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); return; } - final boolean useHttps = url.getProtocol().equals("https"); final RequestContext context = new RequestContext(source, target, callbackContext); synchronized (activeRequests) { @@ -295,27 +293,15 @@ public class FileTransfer extends CordovaPlugin { //------------------ CLIENT REQUEST // Open a HTTP connection to the URL based on protocol - if (useHttps) { - // Using standard HTTPS connection. Will not allow self signed certificate - if (!trustEveryone) { - conn = (HttpsURLConnection) httpClient.open(url); - } - // Use our HTTPS connection that blindly trusts everyone. - // This should only be used in debug environments - else { - // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection) httpClient.open(url); - oldSocketFactory = trustAllHosts(https); - // Save the current hostnameVerifier - oldHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - conn = https; - } - } - // Return a standard HTTP connection - else { - conn = httpClient.open(url); + conn = resourceApi.createHttpConnection(targetUri); + if (useHttps && trustEveryone) { + // Setup the HTTPS connection class to trust everyone + HttpsURLConnection https = (HttpsURLConnection)conn; + oldSocketFactory = trustAllHosts(https); + // Save the current hostnameVerifier + oldHostnameVerifier = https.getHostnameVerifier(); + // Setup the connection not to verify hostnames + https.setHostnameVerifier(DO_NOT_VERIFY); } // Allow Inputs @@ -372,11 +358,11 @@ public class FileTransfer extends CordovaPlugin { // Get a input stream of the file on the phone - InputStream sourceInputStream = getPathFromUri(source); + OpenForReadResult readResult = resourceApi.openForRead(sourceUri); int stringLength = beforeDataBytes.length + tailParamsBytes.length; - if (sourceInputStream instanceof FileInputStream) { - fixedLength = (int) ((FileInputStream)sourceInputStream).getChannel().size() + stringLength; + if (readResult.length >= 0) { + fixedLength = (int)readResult.length + stringLength; progress.setLengthComputable(true); progress.setTotal(fixedLength); } @@ -412,12 +398,12 @@ public class FileTransfer extends CordovaPlugin { totalBytes += beforeDataBytes.length; // create a buffer of maximum size - int bytesAvailable = sourceInputStream.available(); + int bytesAvailable = readResult.inputStream.available(); int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); byte[] buffer = new byte[bufferSize]; // read file and write it into form... - int bytesRead = sourceInputStream.read(buffer, 0, bufferSize); + int bytesRead = readResult.inputStream.read(buffer, 0, bufferSize); long prevBytesRead = 0; while (bytesRead > 0) { @@ -428,9 +414,9 @@ public class FileTransfer extends CordovaPlugin { prevBytesRead = totalBytes; Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); } - bytesAvailable = sourceInputStream.available(); + bytesAvailable = readResult.inputStream.available(); bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); - bytesRead = sourceInputStream.read(buffer, 0, bufferSize); + bytesRead = readResult.inputStream.read(buffer, 0, bufferSize); // Send a progress event. progress.setLoaded(totalBytes); @@ -444,7 +430,7 @@ public class FileTransfer extends CordovaPlugin { totalBytes += tailParamsBytes.length; sendStream.flush(); } finally { - safeClose(sourceInputStream); + safeClose(readResult.inputStream); safeClose(sendStream); } context.currentOutputStream = null; @@ -534,9 +520,9 @@ public class FileTransfer extends CordovaPlugin { private static TrackingInputStream getInputStream(URLConnection conn) throws IOException { String encoding = conn.getContentEncoding(); if (encoding != null && encoding.equalsIgnoreCase("gzip")) { - return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream())); + return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream())); } - return new TrackingHTTPInputStream(conn.getInputStream()); + return new SimpleTrackingInputStream(conn.getInputStream()); } // always verify the host - don't check for certificate @@ -607,7 +593,8 @@ public class FileTransfer extends CordovaPlugin { body = bodyBuilder.toString(); } } - } catch (IOException e) { + // IOException can leave connection object in a bad state, so catch all exceptions. + } catch (Throwable e) { Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e); } } @@ -617,7 +604,7 @@ public class FileTransfer extends CordovaPlugin { /** * Create an error object based on the passed in errorCode - * @param errorCode the error + * @param errorCode the error * @return JSONObject containing the error */ private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) { @@ -642,8 +629,8 @@ public class FileTransfer extends CordovaPlugin { /** * Convenience method to read a parameter from the list of JSON args. - * @param args the args passed to the Plugin - * @param position the position to retrieve the arg from + * @param args the args passed to the Plugin + * @param position the position to retrieve the arg from * @param defaultString the default to be used if the arg does not exist * @return String with the retrieved value */ @@ -662,27 +649,35 @@ public class FileTransfer extends CordovaPlugin { * Downloads a file form a given URL and saves it to the specified directory. * * @param source URL of the server to receive the file - * @param target Full path of the file on the file system + * @param target Full path of the file on the file system */ private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException { Log.d(LOG_TAG, "download " + source + " to " + target); + final CordovaResourceApi resourceApi = webView.getResourceApi(); + final boolean trustEveryone = args.optBoolean(2); final String objectId = args.getString(3); final JSONObject headers = args.optJSONObject(4); + + final Uri sourceUri = resourceApi.remapUri(Uri.parse(source)); + // Accept a path or a URI for the source. + Uri tmpTarget = Uri.parse(target); + final Uri targetUri = resourceApi.remapUri( + tmpTarget.getScheme() != null ? tmpTarget : Uri.fromFile(new File(target))); - final URL url; - try { - url = new URL(source); - } catch (MalformedURLException e) { + int uriType = CordovaResourceApi.getUriType(sourceUri); + final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS; + final boolean isLocalTransfer = !useHttps && uriType != CordovaResourceApi.URI_TYPE_HTTP; + if (uriType == CordovaResourceApi.URI_TYPE_UNKNOWN) { JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, error.toString(), e); + Log.e(LOG_TAG, "Unsupported URI: " + targetUri); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); return; } - final boolean useHttps = url.getProtocol().equals("https"); - - if (!Config.isUrlWhiteListed(source)) { + + // TODO: refactor to also allow resources & content: + if (!isLocalTransfer && !Config.isUrlWhiteListed(source)) { Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'"); JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); @@ -700,81 +695,75 @@ public class FileTransfer extends CordovaPlugin { if (context.aborted) { return; } - URLConnection connection = null; + HttpURLConnection connection = null; HostnameVerifier oldHostnameVerifier = null; SSLSocketFactory oldSocketFactory = null; File file = null; PluginResult result = null; + TrackingInputStream inputStream = null; + OutputStream outputStream = null; try { - file = getFileFromPath(target); + OpenForReadResult readResult = null; + outputStream = resourceApi.openOutputStream(targetUri); + + file = resourceApi.mapUriToFile(targetUri); context.targetFile = file; - // create needed directories - file.getParentFile().mkdirs(); - - // connect to server - // Open a HTTP connection to the URL based on protocol - if (useHttps) { - // Using standard HTTPS connection. Will not allow self signed certificate - if (!trustEveryone) { - connection = (HttpsURLConnection) httpClient.open(url); + + Log.d(LOG_TAG, "Download file:" + sourceUri); + + FileProgressResult progress = new FileProgressResult(); + + if (isLocalTransfer) { + readResult = resourceApi.openForRead(sourceUri); + if (readResult.length != -1) { + progress.setLengthComputable(true); + progress.setTotal(readResult.length); } - // Use our HTTPS connection that blindly trusts everyone. - // This should only be used in debug environments - else { + inputStream = new SimpleTrackingInputStream(readResult.inputStream); + } else { + // connect to server + // Open a HTTP connection to the URL based on protocol + connection = resourceApi.createHttpConnection(sourceUri); + if (useHttps && trustEveryone) { // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection) httpClient.open(url); + HttpsURLConnection https = (HttpsURLConnection)connection; oldSocketFactory = trustAllHosts(https); // Save the current hostnameVerifier oldHostnameVerifier = https.getHostnameVerifier(); // Setup the connection not to verify hostnames https.setHostnameVerifier(DO_NOT_VERIFY); - connection = https; } - } - // Return a standard HTTP connection - else { - connection = httpClient.open(url); - - } + + connection.setRequestMethod("GET"); + + // TODO: Make OkHttp use this CookieManager by default. + String cookie = CookieManager.getInstance().getCookie(sourceUri.toString()); + if(cookie != null) + { + connection.setRequestProperty("cookie", cookie); + } + + // This must be explicitly set for gzip progress tracking to work. + connection.setRequestProperty("Accept-Encoding", "gzip"); - if (connection instanceof HttpURLConnection) { - ((HttpURLConnection)connection).setRequestMethod("GET"); - } + // Handle the other headers + if (headers != null) { + addHeadersToRequest(connection, headers); + } + + connection.connect(); - //Add cookie support - String cookie = CookieManager.getInstance().getCookie(source); - if(cookie != null) - { - connection.setRequestProperty("cookie", cookie); + if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) { + // Only trust content-length header if we understand + // the encoding -- identity or gzip + progress.setLengthComputable(true); + progress.setTotal(connection.getContentLength()); + } + inputStream = getInputStream(connection); } - // This must be explicitly set for gzip progress tracking to work. - connection.setRequestProperty("Accept-Encoding", "gzip"); - - // Handle the other headers - if (headers != null) { - addHeadersToRequest(connection, headers); - } - - connection.connect(); - - Log.d(LOG_TAG, "Download file:" + url); - - FileProgressResult progress = new FileProgressResult(); - if (connection.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) { - // Only trust content-length header if we understand - // the encoding -- identity or gzip - progress.setLengthComputable(true); - progress.setTotal(connection.getContentLength()); - } - - FileOutputStream outputStream = null; - TrackingInputStream inputStream = null; - try { - inputStream = getInputStream(connection); - outputStream = new FileOutputStream(file); synchronized (context) { if (context.aborted) { return; @@ -821,6 +810,7 @@ public class FileTransfer extends CordovaPlugin { Log.e(LOG_TAG, error.toString(), e); result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); } finally { + safeClose(outputStream); synchronized (activeRequests) { activeRequests.remove(objectId); } @@ -847,49 +837,6 @@ public class FileTransfer extends CordovaPlugin { }); } - /** - * Get an input stream based on file path or content:// uri - * - * @param path foo - * @return an input stream - * @throws FileNotFoundException - */ - private InputStream getPathFromUri(String path) throws FileNotFoundException { - try { - InputStream stream = FileHelper.getInputStreamFromUriString(path, cordova); - if (stream == null) { - return new FileInputStream(path); - } else { - return stream; - } - } catch (IOException e) { - throw new FileNotFoundException(); - } - } - - /** - * Get a File object from the passed in path - * - * @param path file path - * @return file object - */ - private File getFileFromPath(String path) throws FileNotFoundException { - File file; - String prefix = "file://"; - - if (path.startsWith(prefix)) { - file = new File(path.substring(prefix.length())); - } else { - file = new File(path); - } - - if (file.getParent() == null) { - throw new FileNotFoundException(); - } - - return file; - } - /** * Abort an ongoing upload or download. */