Update snapshot of FileTransfer.java to

cordova-plugin-file-transfer@9d2246e85c7f4b002218e6d231c0d452a9650cf7
This commit is contained in:
Andrew Grieve 2013-10-22 15:36:04 -04:00
parent 93b9b53acb
commit 57e92e8a56

View File

@ -22,19 +22,14 @@ import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -52,9 +47,10 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
import org.apache.cordova.api.CallbackContext; import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult; import org.apache.cordova.api.PluginResult;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -64,8 +60,6 @@ import android.os.Build;
import android.util.Log; import android.util.Log;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import com.squareup.okhttp.OkHttpClient;
public class FileTransfer extends CordovaPlugin { public class FileTransfer extends CordovaPlugin {
private static final String LOG_TAG = "FileTransfer"; private static final String LOG_TAG = "FileTransfer";
@ -81,8 +75,6 @@ public class FileTransfer extends CordovaPlugin {
private static HashMap<String, RequestContext> activeRequests = new HashMap<String, RequestContext>(); private static HashMap<String, RequestContext> activeRequests = new HashMap<String, RequestContext>();
private static final int MAX_BUFFER_SIZE = 16 * 1024; private static final int MAX_BUFFER_SIZE = 16 * 1024;
private static OkHttpClient httpClient = new OkHttpClient();
private static final class RequestContext { private static final class RequestContext {
String source; String source;
String target; String target;
@ -111,20 +103,20 @@ public class FileTransfer extends CordovaPlugin {
* the HTTP Content-Length header value from the server. * the HTTP Content-Length header value from the server.
*/ */
private static abstract class TrackingInputStream extends FilterInputStream { private static abstract class TrackingInputStream extends FilterInputStream {
public TrackingInputStream(final InputStream in) { public TrackingInputStream(final InputStream in) {
super(in); super(in);
} }
public abstract long getTotalRawBytesRead(); public abstract long getTotalRawBytesRead();
} }
private static class ExposedGZIPInputStream extends GZIPInputStream { private static class ExposedGZIPInputStream extends GZIPInputStream {
public ExposedGZIPInputStream(final InputStream in) throws IOException { public ExposedGZIPInputStream(final InputStream in) throws IOException {
super(in); super(in);
} }
public Inflater getInflater() { public Inflater getInflater() {
return inf; return inf;
} }
} }
/** /**
* Provides raw bytes-read tracking for a GZIP input stream. Reports the * 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. * number of uncompressed bytes.
*/ */
private static class TrackingGZIPInputStream extends TrackingInputStream { private static class TrackingGZIPInputStream extends TrackingInputStream {
private ExposedGZIPInputStream gzin; private ExposedGZIPInputStream gzin;
public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException { public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException {
super(gzin); super(gzin);
this.gzin = gzin; this.gzin = gzin;
} }
public long getTotalRawBytesRead() { public long getTotalRawBytesRead() {
return gzin.getInflater().getBytesRead(); return gzin.getInflater().getBytesRead();
} }
} }
/** /**
* Provides simple total-bytes-read tracking for an existing InputStream * 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; private long bytesRead = 0;
public TrackingHTTPInputStream(InputStream stream) { public SimpleTrackingInputStream(InputStream stream) {
super(stream); super(stream);
} }
private int updateBytesRead(int newBytesRead) { private int updateBytesRead(int newBytesRead) {
if (newBytesRead != -1) { if (newBytesRead != -1) {
bytesRead += newBytesRead; bytesRead += newBytesRead;
} }
return newBytesRead; return newBytesRead;
} }
@Override @Override
@ -174,7 +166,7 @@ public class FileTransfer extends CordovaPlugin {
} }
public long getTotalRawBytesRead() { public long getTotalRawBytesRead() {
return bytesRead; return bytesRead;
} }
} }
@ -252,6 +244,8 @@ public class FileTransfer extends CordovaPlugin {
final String objectId = args.getString(9); final String objectId = args.getString(9);
final String httpMethod = getArgument(args, 10, "POST"); final String httpMethod = getArgument(args, 10, "POST");
final CordovaResourceApi resourceApi = webView.getResourceApi();
Log.d(LOG_TAG, "fileKey: " + fileKey); Log.d(LOG_TAG, "fileKey: " + fileKey);
Log.d(LOG_TAG, "fileName: " + fileName); Log.d(LOG_TAG, "fileName: " + fileName);
Log.d(LOG_TAG, "mimeType: " + mimeType); Log.d(LOG_TAG, "mimeType: " + mimeType);
@ -262,16 +256,20 @@ public class FileTransfer extends CordovaPlugin {
Log.d(LOG_TAG, "objectId: " + objectId); Log.d(LOG_TAG, "objectId: " + objectId);
Log.d(LOG_TAG, "httpMethod: " + httpMethod); Log.d(LOG_TAG, "httpMethod: " + httpMethod);
final URL url; final Uri targetUri = resourceApi.remapUri(Uri.parse(target));
try { // Accept a path or a URI for the source.
url = new URL(target); Uri tmpSrc = Uri.parse(source);
} catch (MalformedURLException e) { 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); 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)); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return; return;
} }
final boolean useHttps = url.getProtocol().equals("https");
final RequestContext context = new RequestContext(source, target, callbackContext); final RequestContext context = new RequestContext(source, target, callbackContext);
synchronized (activeRequests) { synchronized (activeRequests) {
@ -295,27 +293,15 @@ public class FileTransfer extends CordovaPlugin {
//------------------ CLIENT REQUEST //------------------ CLIENT REQUEST
// Open a HTTP connection to the URL based on protocol // Open a HTTP connection to the URL based on protocol
if (useHttps) { conn = resourceApi.createHttpConnection(targetUri);
// Using standard HTTPS connection. Will not allow self signed certificate if (useHttps && trustEveryone) {
if (!trustEveryone) { // Setup the HTTPS connection class to trust everyone
conn = (HttpsURLConnection) httpClient.open(url); HttpsURLConnection https = (HttpsURLConnection)conn;
} oldSocketFactory = trustAllHosts(https);
// Use our HTTPS connection that blindly trusts everyone. // Save the current hostnameVerifier
// This should only be used in debug environments oldHostnameVerifier = https.getHostnameVerifier();
else { // Setup the connection not to verify hostnames
// Setup the HTTPS connection class to trust everyone https.setHostnameVerifier(DO_NOT_VERIFY);
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);
} }
// Allow Inputs // Allow Inputs
@ -372,11 +358,11 @@ public class FileTransfer extends CordovaPlugin {
// Get a input stream of the file on the phone // 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; int stringLength = beforeDataBytes.length + tailParamsBytes.length;
if (sourceInputStream instanceof FileInputStream) { if (readResult.length >= 0) {
fixedLength = (int) ((FileInputStream)sourceInputStream).getChannel().size() + stringLength; fixedLength = (int)readResult.length + stringLength;
progress.setLengthComputable(true); progress.setLengthComputable(true);
progress.setTotal(fixedLength); progress.setTotal(fixedLength);
} }
@ -412,12 +398,12 @@ public class FileTransfer extends CordovaPlugin {
totalBytes += beforeDataBytes.length; totalBytes += beforeDataBytes.length;
// create a buffer of maximum size // create a buffer of maximum size
int bytesAvailable = sourceInputStream.available(); int bytesAvailable = readResult.inputStream.available();
int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
// read file and write it into form... // 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; long prevBytesRead = 0;
while (bytesRead > 0) { while (bytesRead > 0) {
@ -428,9 +414,9 @@ public class FileTransfer extends CordovaPlugin {
prevBytesRead = totalBytes; prevBytesRead = totalBytes;
Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes");
} }
bytesAvailable = sourceInputStream.available(); bytesAvailable = readResult.inputStream.available();
bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
bytesRead = sourceInputStream.read(buffer, 0, bufferSize); bytesRead = readResult.inputStream.read(buffer, 0, bufferSize);
// Send a progress event. // Send a progress event.
progress.setLoaded(totalBytes); progress.setLoaded(totalBytes);
@ -444,7 +430,7 @@ public class FileTransfer extends CordovaPlugin {
totalBytes += tailParamsBytes.length; totalBytes += tailParamsBytes.length;
sendStream.flush(); sendStream.flush();
} finally { } finally {
safeClose(sourceInputStream); safeClose(readResult.inputStream);
safeClose(sendStream); safeClose(sendStream);
} }
context.currentOutputStream = null; context.currentOutputStream = null;
@ -534,9 +520,9 @@ public class FileTransfer extends CordovaPlugin {
private static TrackingInputStream getInputStream(URLConnection conn) throws IOException { private static TrackingInputStream getInputStream(URLConnection conn) throws IOException {
String encoding = conn.getContentEncoding(); String encoding = conn.getContentEncoding();
if (encoding != null && encoding.equalsIgnoreCase("gzip")) { 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 // always verify the host - don't check for certificate
@ -607,7 +593,8 @@ public class FileTransfer extends CordovaPlugin {
body = bodyBuilder.toString(); 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); 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 * Create an error object based on the passed in errorCode
* @param errorCode the error * @param errorCode the error
* @return JSONObject containing the error * @return JSONObject containing the error
*/ */
private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) { 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. * Convenience method to read a parameter from the list of JSON args.
* @param args the args passed to the Plugin * @param args the args passed to the Plugin
* @param position the position to retrieve the arg from * @param position the position to retrieve the arg from
* @param defaultString the default to be used if the arg does not exist * @param defaultString the default to be used if the arg does not exist
* @return String with the retrieved value * @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. * 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 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 { private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(LOG_TAG, "download " + source + " to " + target); Log.d(LOG_TAG, "download " + source + " to " + target);
final CordovaResourceApi resourceApi = webView.getResourceApi();
final boolean trustEveryone = args.optBoolean(2); final boolean trustEveryone = args.optBoolean(2);
final String objectId = args.getString(3); final String objectId = args.getString(3);
final JSONObject headers = args.optJSONObject(4); final JSONObject headers = args.optJSONObject(4);
final URL url; final Uri sourceUri = resourceApi.remapUri(Uri.parse(source));
try { // Accept a path or a URI for the source.
url = new URL(source); Uri tmpTarget = Uri.parse(target);
} catch (MalformedURLException e) { final Uri targetUri = resourceApi.remapUri(
tmpTarget.getScheme() != null ? tmpTarget : Uri.fromFile(new File(target)));
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); 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)); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
return; 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 + "'"); Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401); JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
@ -700,81 +695,75 @@ public class FileTransfer extends CordovaPlugin {
if (context.aborted) { if (context.aborted) {
return; return;
} }
URLConnection connection = null; HttpURLConnection connection = null;
HostnameVerifier oldHostnameVerifier = null; HostnameVerifier oldHostnameVerifier = null;
SSLSocketFactory oldSocketFactory = null; SSLSocketFactory oldSocketFactory = null;
File file = null; File file = null;
PluginResult result = null; PluginResult result = null;
TrackingInputStream inputStream = null;
OutputStream outputStream = null;
try { try {
file = getFileFromPath(target); OpenForReadResult readResult = null;
context.targetFile = file; outputStream = resourceApi.openOutputStream(targetUri);
// create needed directories
file.getParentFile().mkdirs();
// connect to server file = resourceApi.mapUriToFile(targetUri);
// Open a HTTP connection to the URL based on protocol context.targetFile = file;
if (useHttps) {
// Using standard HTTPS connection. Will not allow self signed certificate Log.d(LOG_TAG, "Download file:" + sourceUri);
if (!trustEveryone) {
connection = (HttpsURLConnection) httpClient.open(url); 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. inputStream = new SimpleTrackingInputStream(readResult.inputStream);
// This should only be used in debug environments } else {
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 // Setup the HTTPS connection class to trust everyone
HttpsURLConnection https = (HttpsURLConnection) httpClient.open(url); HttpsURLConnection https = (HttpsURLConnection)connection;
oldSocketFactory = trustAllHosts(https); oldSocketFactory = trustAllHosts(https);
// Save the current hostnameVerifier // Save the current hostnameVerifier
oldHostnameVerifier = https.getHostnameVerifier(); oldHostnameVerifier = https.getHostnameVerifier();
// Setup the connection not to verify hostnames // Setup the connection not to verify hostnames
https.setHostnameVerifier(DO_NOT_VERIFY); https.setHostnameVerifier(DO_NOT_VERIFY);
connection = https;
} }
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");
// Handle the other headers
if (headers != null) {
addHeadersToRequest(connection, headers);
}
connection.connect();
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);
} }
// Return a standard HTTP connection
else {
connection = httpClient.open(url);
}
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection).setRequestMethod("GET");
}
//Add cookie support
String cookie = CookieManager.getInstance().getCookie(source);
if(cookie != null)
{
connection.setRequestProperty("cookie", cookie);
}
// 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 { try {
inputStream = getInputStream(connection);
outputStream = new FileOutputStream(file);
synchronized (context) { synchronized (context) {
if (context.aborted) { if (context.aborted) {
return; return;
@ -821,6 +810,7 @@ public class FileTransfer extends CordovaPlugin {
Log.e(LOG_TAG, error.toString(), e); Log.e(LOG_TAG, error.toString(), e);
result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} finally { } finally {
safeClose(outputStream);
synchronized (activeRequests) { synchronized (activeRequests) {
activeRequests.remove(objectId); 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. * Abort an ongoing upload or download.
*/ */