diff --git a/VERSION b/VERSION
index bd8bf882..92f201f4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.7.0
+1.8.0rc1
diff --git a/bin/templates/project/cordova/templates/project/assets/www/index.html b/bin/templates/project/cordova/templates/project/assets/www/index.html
index 2bf3dda5..39368e0d 100644
--- a/bin/templates/project/cordova/templates/project/assets/www/index.html
+++ b/bin/templates/project/cordova/templates/project/assets/www/index.html
@@ -23,7 +23,7 @@
PhoneGap
-
+
diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js
index f43e67b0..ddbb4b5e 100644
--- a/framework/assets/js/cordova.android.js
+++ b/framework/assets/js/cordova.android.js
@@ -1,6 +1,6 @@
-// commit 4a4ba9985c920850fe3f229abc60de984e196ab5
+// commit 95f199e1c207dc89b84e79a9a7b27d6a3cc8fe14
-// File generated at :: Fri May 18 2012 13:43:11 GMT-0700 (PDT)
+// File generated at :: Thu May 24 2012 21:36:17 GMT-0400 (EDT)
/*
Licensed to the Apache Software Foundation (ASF) under one
@@ -714,6 +714,9 @@ module.exports = {
children: {
exec: {
path: 'cordova/exec'
+ },
+ logger: {
+ path: 'cordova/plugin/logger'
}
}
},
@@ -1195,7 +1198,7 @@ for (var key in Camera) {
* Gets a picture from source defined by "options.sourceType", and returns the
* image as defined by the "options.destinationType" option.
- * The defaults are sourceType=CAMERA and destinationType=FILE_URL.
+ * The defaults are sourceType=CAMERA and destinationType=FILE_URI.
*
* @param {Function} successCallback
* @param {Function} errorCallback
@@ -2141,7 +2144,7 @@ Entry.prototype.toURL = function() {
Entry.prototype.toURI = function(mimeType) {
console.log("DEPRECATED: Update your code to use 'toURL'");
// fullPath attribute contains the full URI
- return this.fullPath;
+ return this.toURL();
};
/**
@@ -4569,6 +4572,177 @@ var exec = require('cordova/exec'),
module.exports = compass;
});
+// file: lib/common/plugin/console-via-logger.js
+define("cordova/plugin/console-via-logger", function(require, exports, module) {
+//------------------------------------------------------------------------------
+
+var logger = require("cordova/plugin/logger");
+var utils = require("cordova/utils");
+
+//------------------------------------------------------------------------------
+// object that we're exporting
+//------------------------------------------------------------------------------
+var console = module.exports;
+
+//------------------------------------------------------------------------------
+// copy of the original console object
+//------------------------------------------------------------------------------
+var WinConsole = window.console;
+
+//------------------------------------------------------------------------------
+// whether to use the logger
+//------------------------------------------------------------------------------
+var UseLogger = false;
+
+//------------------------------------------------------------------------------
+// Timers
+//------------------------------------------------------------------------------
+var Timers = {};
+
+//------------------------------------------------------------------------------
+// used for unimplemented methods
+//------------------------------------------------------------------------------
+function noop() {}
+
+//------------------------------------------------------------------------------
+// used for unimplemented methods
+//------------------------------------------------------------------------------
+console.useLogger = function (value) {
+ if (arguments.length) UseLogger = !!value;
+
+ if (UseLogger) {
+ if (logger.useConsole()) {
+ throw new Error("console and logger are too intertwingly");
+ }
+ }
+
+ return UseLogger;
+};
+
+//------------------------------------------------------------------------------
+console.log = function() {
+ if (logger.useConsole()) return;
+ logger.log.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.error = function() {
+ if (logger.useConsole()) return;
+ logger.error.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.warn = function() {
+ if (logger.useConsole()) return;
+ logger.warn.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.info = function() {
+ if (logger.useConsole()) return;
+ logger.info.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.debug = function() {
+ if (logger.useConsole()) return;
+ logger.debug.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.assert = function(expression) {
+ if (expression) return;
+
+ var message = utils.vformat(arguments[1], [].slice.call(arguments, 2));
+ console.log("ASSERT: " + message);
+};
+
+//------------------------------------------------------------------------------
+console.clear = function() {};
+
+//------------------------------------------------------------------------------
+console.dir = function(object) {
+ console.log("%o", object);
+};
+
+//------------------------------------------------------------------------------
+console.dirxml = function(node) {
+ console.log(node.innerHTML);
+};
+
+//------------------------------------------------------------------------------
+console.trace = noop;
+
+//------------------------------------------------------------------------------
+console.group = console.log;
+
+//------------------------------------------------------------------------------
+console.groupCollapsed = console.log;
+
+//------------------------------------------------------------------------------
+console.groupEnd = noop;
+
+//------------------------------------------------------------------------------
+console.time = function(name) {
+ Timers[name] = new Date().valueOf();
+};
+
+//------------------------------------------------------------------------------
+console.timeEnd = function(name) {
+ var timeStart = Timers[name];
+ if (!timeStart) {
+ console.warn("unknown timer: " + name);
+ return;
+ }
+
+ var timeElapsed = new Date().valueOf() - timeStart;
+ console.log(name + ": " + timeElapsed + "ms");
+};
+
+//------------------------------------------------------------------------------
+console.timeStamp = noop;
+
+//------------------------------------------------------------------------------
+console.profile = noop;
+
+//------------------------------------------------------------------------------
+console.profileEnd = noop;
+
+//------------------------------------------------------------------------------
+console.count = noop;
+
+//------------------------------------------------------------------------------
+console.exception = console.log;
+
+//------------------------------------------------------------------------------
+console.table = function(data, columns) {
+ console.log("%o", data);
+};
+
+//------------------------------------------------------------------------------
+// return a new function that calls both functions passed as args
+//------------------------------------------------------------------------------
+function wrapperedOrigCall(orgFunc, newFunc) {
+ return function() {
+ var args = [].slice.call(arguments);
+ try { orgFunc.apply(WinConsole, args); } catch (e) {}
+ try { newFunc.apply(console, args); } catch (e) {}
+ };
+}
+
+//------------------------------------------------------------------------------
+// For every function that exists in the original console object, that
+// also exists in the new console object, wrap the new console method
+// with one that calls both
+//------------------------------------------------------------------------------
+for (var key in console) {
+ if (typeof WinConsole[key] == "function") {
+ console[key] = wrapperedOrigCall(WinConsole[key], console[key]);
+ }
+}
+
+});
+
// file: lib/common/plugin/contacts.js
define("cordova/plugin/contacts", function(require, exports, module) {
var exec = require('cordova/exec'),
@@ -4830,6 +5004,233 @@ module.exports = geolocation;
});
+// file: lib/common/plugin/logger.js
+define("cordova/plugin/logger", function(require, exports, module) {
+//------------------------------------------------------------------------------
+// The logger module exports the following properties/functions:
+//
+// LOG - constant for the level LOG
+// ERROR - constant for the level ERROR
+// WARN - constant for the level WARN
+// INFO - constant for the level INFO
+// DEBUG - constant for the level DEBUG
+// logLevel() - returns current log level
+// logLevel(value) - sets and returns a new log level
+// useConsole() - returns whether logger is using console
+// useConsole(value) - sets and returns whether logger is using console
+// log(message,...) - logs a message at level LOG
+// error(message,...) - logs a message at level ERROR
+// warn(message,...) - logs a message at level WARN
+// info(message,...) - logs a message at level INFO
+// debug(message,...) - logs a message at level DEBUG
+// logLevel(level,message,...) - logs a message specified level
+//
+//------------------------------------------------------------------------------
+
+var logger = exports;
+
+var exec = require('cordova/exec');
+var utils = require('cordova/utils');
+
+var UseConsole = true;
+var Queued = [];
+var DeviceReady = false;
+var CurrentLevel;
+
+/**
+ * Logging levels
+ */
+
+var Levels = [
+ "LOG",
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG"
+];
+
+/*
+ * add the logging levels to the logger object and
+ * to a separate levelsMap object for testing
+ */
+
+var LevelsMap = {};
+for (var i=0; i CurrentLevel) return;
+
+ // queue the message if not yet at deviceready
+ if (!DeviceReady && !UseConsole) {
+ Queued.push([level, message]);
+ return;
+ }
+
+ // if not using the console, use the native logger
+ if (!UseConsole) {
+ exec(null, null, "Logger", "logLevel", [level, message]);
+ return;
+ }
+
+ // make sure console is not using logger
+ if (console.__usingCordovaLogger) {
+ throw new Error("console and logger are too intertwingly");
+ }
+
+ // log to the console
+ switch (level) {
+ case logger.LOG: console.log(message); break;
+ case logger.ERROR: console.log("ERROR: " + message); break;
+ case logger.WARN: console.log("WARN: " + message); break;
+ case logger.INFO: console.log("INFO: " + message); break;
+ case logger.DEBUG: console.log("DEBUG: " + message); break;
+ }
+};
+
+// when deviceready fires, log queued messages
+logger.__onDeviceReady = function() {
+ if (DeviceReady) return;
+
+ DeviceReady = true;
+
+ for (var i=0; i 2) {
+ setTimeout( function() {
+ fail(FileError.ENCODING_ERR);
+ },0);
+ return;
+ }
// if successful, return either a file or directory entry
var success = function(entry) {
var result;
-
if (entry) {
if (typeof successCallback === 'function') {
// create appropriate Entry object
@@ -5058,6 +5465,68 @@ var splashscreen = {
module.exports = splashscreen;
});
+// file: lib/common/plugin/widget.js
+define("cordova/plugin/widget", function(require, exports, module) {
+var exec = require('cordova/exec'),
+ cordova = require('cordova'),
+ channel = require('cordova/channel');
+
+var Widget = function () {
+ this.author = null;
+ this.description = null;
+ this.name = null;
+ this.shortName = null;
+ this.version = null;
+ this.id = null;
+ this.authorEmail = null;
+ this.authorHref = null;
+ this._firstRun = true;
+
+ var me = this;
+
+ channel.onCordovaReady.subscribeOnce(function() {
+ me.getInfo(function (info) {
+ me.author = info.author;
+ me.description = info.description;
+ me.name = info.name;
+ me.shortName = info.shortName;
+ me.version = info.version;
+ me.id = info.id;
+ me.authorEmail = info.authorEmail;
+ me.authorHref = info.authorHref;
+
+ // should only fire this once
+ if (me._firstRun) {
+ me._firstRun = false;
+ channel.onCordovaAppInfoReady.fire();
+ }
+ },
+ function (e) {
+ // If we can't get the network info we should still tell Cordova
+ // to fire the deviceready event.
+ if (me._firstRun) {
+ me._firstRun = false;
+ channel.onCordovaAppInfoReady.fire();
+ }
+ console.log("Error initializing Widget: " + e);
+ });
+ });
+};
+
+/**
+ * Get connection info
+ *
+ * @param {Function} successCallback The function to call when the Connection data is available
+ * @param {Function} errorCallback The function to call when there is an error getting the Connection data. (OPTIONAL)
+ */
+Widget.prototype.getInfo = function (successCallback, errorCallback) {
+ // Get info
+ exec(successCallback, errorCallback, "Widget", "getApplicationInfo", []);
+};
+
+module.exports = new Widget();
+});
+
// file: lib/common/utils.js
define("cordova/utils", function(require, exports, module) {
var utils = exports;
@@ -5159,6 +5628,16 @@ utils.alert = function(msg) {
/**
* Formats a string and arguments following it ala sprintf()
*
+ * see utils.vformat() for more information
+ */
+utils.format = function(formatString /* ,... */) {
+ var args = [].slice.call(arguments, 1);
+ return utils.vformat(formatString, args);
+};
+
+/**
+ * Formats a string and arguments following it ala vsprintf()
+ *
* format chars:
* %j - format arg as JSON
* %o - format arg as JSON
@@ -5170,14 +5649,13 @@ utils.alert = function(msg) {
* for rationale, see FireBug's Console API:
* http://getfirebug.com/wiki/index.php/Console_API
*/
-utils.format = function(formatString /* ,... */) {
+utils.vformat = function(formatString, args) {
if (formatString === null || formatString === undefined) return "";
if (arguments.length == 1) return formatString.toString();
var pattern = /(.*?)%(.)(.*)/;
var rest = formatString.toString();
var result = [];
- var args = [].slice.call(arguments,1);
while (args.length) {
var arg = args.shift();
diff --git a/framework/assets/www/index.html b/framework/assets/www/index.html
index 0ca839ab..1f39dc57 100644
--- a/framework/assets/www/index.html
+++ b/framework/assets/www/index.html
@@ -19,7 +19,7 @@
-
+
diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java
index ba5d9243..e6fe1531 100755
--- a/framework/src/org/apache/cordova/CameraLauncher.java
+++ b/framework/src/org/apache/cordova/CameraLauncher.java
@@ -256,7 +256,9 @@ public class CameraLauncher extends Plugin {
}
}
- return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+ Bitmap retval = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+ bitmap.recycle();
+ return retval;
}
/**
diff --git a/framework/src/org/apache/cordova/Device.java b/framework/src/org/apache/cordova/Device.java
index 09a7a47d..a8433561 100644
--- a/framework/src/org/apache/cordova/Device.java
+++ b/framework/src/org/apache/cordova/Device.java
@@ -38,7 +38,7 @@ import android.telephony.TelephonyManager;
public class Device extends Plugin {
public static final String TAG = "Device";
- public static String cordovaVersion = "1.7.0"; // Cordova version
+ public static String cordovaVersion = "1.8.0rc1"; // Cordova version
public static String platform = "Android"; // Device OS
public static String uuid; // Device UUID
diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java
index a631a055..368d97ad 100644
--- a/framework/src/org/apache/cordova/FileTransfer.java
+++ b/framework/src/org/apache/cordova/FileTransfer.java
@@ -27,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@@ -35,7 +36,6 @@ import java.util.Iterator;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
@@ -57,7 +57,7 @@ public class FileTransfer extends Plugin {
private static final String LOG_TAG = "FileTransfer";
private static final String LINE_START = "--";
private static final String LINE_END = "\r\n";
- private static final String BOUNDRY = "*****";
+ private static final String BOUNDARY = "*****";
public static int FILE_NOT_FOUND_ERR = 1;
public static int INVALID_URL_ERR = 2;
@@ -82,49 +82,251 @@ public class FileTransfer extends Plugin {
return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target");
}
- try {
- if (action.equals("upload")) {
- // Setup the options
- String fileKey = null;
- String fileName = null;
- String mimeType = null;
+ if (action.equals("upload")) {
+ return upload(source, target, args);
+ } else if (action.equals("download")) {
+ return download(source, target);
+ } else {
+ return new PluginResult(PluginResult.Status.INVALID_ACTION);
+ }
+ }
- fileKey = getArgument(args, 2, "file");
- fileName = getArgument(args, 3, "image.jpg");
- mimeType = getArgument(args, 4, "image/jpeg");
- JSONObject params = args.optJSONObject(5);
- boolean trustEveryone = args.optBoolean(6);
- boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API
- FileUploadResult r = upload(source, target, fileKey, fileName, mimeType, params, trustEveryone, chunkedMode);
- Log.d(LOG_TAG, "****** About to return a result from upload");
- return new PluginResult(PluginResult.Status.OK, r.toJSONObject());
- } else if (action.equals("download")) {
- JSONObject r = download(source, target);
- Log.d(LOG_TAG, "****** About to return a result from download");
- return new PluginResult(PluginResult.Status.OK, r);
- } else {
- return new PluginResult(PluginResult.Status.INVALID_ACTION);
+ /**
+ * Uploads the specified file to the server URL provided using an HTTP multipart request.
+ * @param source Full path of the file on the file system
+ * @param target URL of the server to receive the file
+ * @param args JSON Array of args
+ *
+ * args[2] fileKey Name of file request parameter
+ * args[3] fileName File name to be used on server
+ * args[4] mimeType Describes file content type
+ * args[5] params key:value pairs of user-defined parameters
+ * @return FileUploadResult containing result of upload request
+ */
+ private PluginResult upload(String source, String target, JSONArray args) {
+ Log.d(LOG_TAG, "upload " + source + " to " + target);
+
+ HttpURLConnection conn = null;
+ try {
+ // Setup the options
+ String fileKey = getArgument(args, 2, "file");
+ String fileName = getArgument(args, 3, "image.jpg");
+ String mimeType = getArgument(args, 4, "image/jpeg");
+ JSONObject params = args.optJSONObject(5);
+ if (params == null) params = new JSONObject();
+ boolean trustEveryone = args.optBoolean(6);
+ boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API
+
+ Log.d(LOG_TAG, "fileKey: " + fileKey);
+ Log.d(LOG_TAG, "fileName: " + fileName);
+ Log.d(LOG_TAG, "mimeType: " + mimeType);
+ Log.d(LOG_TAG, "params: " + params);
+ Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
+ Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
+
+ // Create return object
+ FileUploadResult result = new FileUploadResult();
+
+ // Get a input stream of the file on the phone
+ FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source);
+
+ DataOutputStream dos = null;
+
+ int bytesRead, bytesAvailable, bufferSize;
+ long totalBytes;
+ byte[] buffer;
+ int maxBufferSize = 8096;
+
+ //------------------ CLIENT REQUEST
+ // open a URL connection to the server
+ URL url = new URL(target);
+
+ // Open a HTTP connection to the URL based on protocol
+ if (url.getProtocol().toLowerCase().equals("https")) {
+ // Using standard HTTPS connection. Will not allow self signed certificate
+ if (!trustEveryone) {
+ conn = (HttpsURLConnection) url.openConnection();
+ }
+ // 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
+ trustAllHosts();
+ HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
+ // Save the current hostnameVerifier
+ defaultHostnameVerifier = https.getHostnameVerifier();
+ // Setup the connection not to verify hostnames
+ https.setHostnameVerifier(DO_NOT_VERIFY);
+ conn = https;
+ }
}
+ // Return a standard HTTP connection
+ else {
+ conn = (HttpURLConnection) url.openConnection();
+ }
+
+ // Allow Inputs
+ conn.setDoInput(true);
+
+ // Allow Outputs
+ conn.setDoOutput(true);
+
+ // Don't use a cached copy.
+ conn.setUseCaches(false);
+
+ // Use a post method.
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
+
+ // Handle the other headers
+ try {
+ JSONObject headers = params.getJSONObject("headers");
+ for (Iterator iter = headers.keys(); iter.hasNext();)
+ {
+ String headerKey = iter.next().toString();
+ conn.setRequestProperty(headerKey, headers.getString(headerKey));
+ }
+ } catch (JSONException e1) {
+ // No headers to be manipulated!
+ }
+
+ // Set the cookies on the response
+ String cookie = CookieManager.getInstance().getCookie(target);
+ if (cookie != null) {
+ conn.setRequestProperty("Cookie", cookie);
+ }
+
+
+ /*
+ * Store the non-file portions of the multipart data as a string, so that we can add it
+ * to the contentSize, since it is part of the body of the HTTP request.
+ */
+ String extraParams = "";
+ try {
+ for (Iterator iter = params.keys(); iter.hasNext();) {
+ Object key = iter.next();
+ if(!String.valueOf(key).equals("headers"))
+ {
+ extraParams += LINE_START + BOUNDARY + LINE_END;
+ extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";";
+ extraParams += LINE_END + LINE_END;
+ extraParams += params.getString(key.toString());
+ extraParams += LINE_END;
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, e.getMessage(), e);
+ }
+
+ extraParams += LINE_START + BOUNDARY + LINE_END;
+ extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
+
+ String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
+ String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END;
+
+ // Should set this up as an option
+ if (chunkedMode) {
+ conn.setChunkedStreamingMode(maxBufferSize);
+ }
+ else
+ {
+ int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length;
+ Log.d(LOG_TAG, "String Length: " + stringLength);
+ int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
+ Log.d(LOG_TAG, "Content Length: " + fixedLength);
+ conn.setFixedLengthStreamingMode(fixedLength);
+ }
+
+
+ dos = new DataOutputStream( conn.getOutputStream() );
+ dos.writeBytes(extraParams);
+ //We don't want to chagne encoding, we just want this to write for all Unicode.
+ dos.write(fileName.getBytes("UTF-8"));
+ dos.writeBytes(midParams);
+
+ // create a buffer of maximum size
+ bytesAvailable = fileInputStream.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ buffer = new byte[bufferSize];
+
+ // read file and write it into form...
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+ totalBytes = 0;
+
+ while (bytesRead > 0) {
+ totalBytes += bytesRead;
+ result.setBytesSent(totalBytes);
+ dos.write(buffer, 0, bufferSize);
+ bytesAvailable = fileInputStream.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+ }
+
+ // send multipart form data necesssary after file data...
+ dos.writeBytes(tailParams);
+
+ // close streams
+ fileInputStream.close();
+ dos.flush();
+ dos.close();
+
+ //------------------ read the SERVER RESPONSE
+ StringBuffer responseString = new StringBuffer("");
+ DataInputStream inStream;
+ try {
+ inStream = new DataInputStream ( conn.getInputStream() );
+ } catch(FileNotFoundException e) {
+ Log.e(LOG_TAG, e.toString(), e);
+ throw new IOException("Received error from server");
+ }
+
+ String line;
+ while (( line = inStream.readLine()) != null) {
+ responseString.append(line);
+ }
+ Log.d(LOG_TAG, "got response from server");
+ Log.d(LOG_TAG, responseString.toString());
+
+ // send request and retrieve response
+ result.setResponseCode(conn.getResponseCode());
+ result.setResponse(responseString.toString());
+
+ inStream.close();
+
+ // Revert back to the proper verifier and socket factories
+ if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
+ ((HttpsURLConnection) conn).setHostnameVerifier(defaultHostnameVerifier);
+ HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
+ }
+
+ Log.d(LOG_TAG, "****** About to return a result from upload");
+ return new PluginResult(PluginResult.Status.OK, result.toJSONObject());
+
} catch (FileNotFoundException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target);
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
- } catch (IllegalArgumentException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target);
- return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
- } catch (SSLException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- Log.d(LOG_TAG, "Got my ssl exception!!!");
- JSONObject error = createFileTransferError(CONNECTION_ERR, source, target);
+ } catch (MalformedURLException e) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (IOException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- JSONObject error = createFileTransferError(CONNECTION_ERR, source, target);
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+ Log.e(LOG_TAG, error.toString(), e);
return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+ } catch (Throwable t) {
+ // Shouldn't happen, but will
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+ Log.wtf(LOG_TAG, error.toString(), t);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } finally {
+ if (conn != null) {
+ conn.disconnect();
+ }
}
}
@@ -172,18 +374,36 @@ public class FileTransfer extends Plugin {
}
}
- /**
- * Create an error object based on the passed in errorCode
- * @param errorCode the error
- * @return JSONObject containing the error
- */
- private JSONObject createFileTransferError(int errorCode, String source, String target) {
+ private JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) {
+
+ Integer httpStatus = null;
+
+ if (connection != null) {
+ try {
+ httpStatus = connection.getResponseCode();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
+ }
+ }
+
+ return createFileTransferError(errorCode, source, target, httpStatus);
+ }
+
+ /**
+ * Create an error object based on the passed in errorCode
+ * @param errorCode the error
+ * @return JSONObject containing the error
+ */
+ private JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) {
JSONObject error = null;
try {
error = new JSONObject();
error.put("code", errorCode);
error.put("source", source);
error.put("target", target);
+ if (httpStatus != null) {
+ error.put("http_status", httpStatus);
+ }
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
}
@@ -208,198 +428,6 @@ public class FileTransfer extends Plugin {
return arg;
}
- /**
- * Uploads the specified file to the server URL provided using an HTTP
- * multipart request.
- * @param file Full path of the file on the file system
- * @param server URL of the server to receive the file
- * @param fileKey Name of file request parameter
- * @param fileName File name to be used on server
- * @param mimeType Describes file content type
- * @param params key:value pairs of user-defined parameters
- * @return FileUploadResult containing result of upload request
- */
- public FileUploadResult upload(String file, String server, final String fileKey, final String fileName,
- final String mimeType, JSONObject params, boolean trustEveryone, boolean chunkedMode) throws IOException, SSLException {
- // Create return object
- FileUploadResult result = new FileUploadResult();
-
- // Get a input stream of the file on the phone
- FileInputStream fileInputStream = (FileInputStream) getPathFromUri(file);
-
- HttpURLConnection conn = null;
- DataOutputStream dos = null;
-
- int bytesRead, bytesAvailable, bufferSize;
- long totalBytes;
- byte[] buffer;
- int maxBufferSize = 8096;
-
- //------------------ CLIENT REQUEST
- // open a URL connection to the server
- URL url = new URL(server);
-
- // Open a HTTP connection to the URL based on protocol
- if (url.getProtocol().toLowerCase().equals("https")) {
- // Using standard HTTPS connection. Will not allow self signed certificate
- if (!trustEveryone) {
- conn = (HttpsURLConnection) url.openConnection();
- }
- // 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
- trustAllHosts();
- HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
- // Save the current hostnameVerifier
- defaultHostnameVerifier = https.getHostnameVerifier();
- // Setup the connection not to verify hostnames
- https.setHostnameVerifier(DO_NOT_VERIFY);
- conn = https;
- }
- }
- // Return a standard HTTP connection
- else {
- conn = (HttpURLConnection) url.openConnection();
- }
-
- // Allow Inputs
- conn.setDoInput(true);
-
- // Allow Outputs
- conn.setDoOutput(true);
-
- // Don't use a cached copy.
- conn.setUseCaches(false);
-
- // Use a post method.
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Connection", "Keep-Alive");
- conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+BOUNDRY);
-
- // Handle the other headers
- try {
- JSONObject headers = params.getJSONObject("headers");
- for (Iterator iter = headers.keys(); iter.hasNext();)
- {
- String headerKey = iter.next().toString();
- conn.setRequestProperty(headerKey, headers.getString(headerKey));
- }
- } catch (JSONException e1) {
- // No headers to be manipulated!
- }
-
- // Set the cookies on the response
- String cookie = CookieManager.getInstance().getCookie(server);
- if (cookie != null) {
- conn.setRequestProperty("Cookie", cookie);
- }
-
-
- /*
- * Store the non-file portions of the multipart data as a string, so that we can add it
- * to the contentSize, since it is part of the body of the HTTP request.
- */
- String extraParams = "";
- try {
- for (Iterator iter = params.keys(); iter.hasNext();) {
- Object key = iter.next();
- if(key.toString() != "headers")
- {
- extraParams += LINE_START + BOUNDRY + LINE_END;
- extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";";
- extraParams += LINE_END + LINE_END;
- extraParams += params.getString(key.toString());
- extraParams += LINE_END;
- }
- }
- } catch (JSONException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- }
-
- extraParams += LINE_START + BOUNDRY + LINE_END;
- extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
-
- String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
- String tailParams = LINE_END + LINE_START + BOUNDRY + LINE_START + LINE_END;
-
- // Should set this up as an option
- if (chunkedMode) {
- conn.setChunkedStreamingMode(maxBufferSize);
- }
- else
- {
- int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length;
- Log.d(LOG_TAG, "String Length: " + stringLength);
- int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
- Log.d(LOG_TAG, "Content Length: " + fixedLength);
- conn.setFixedLengthStreamingMode(fixedLength);
- }
-
-
- dos = new DataOutputStream( conn.getOutputStream() );
- dos.writeBytes(extraParams);
- //We don't want to chagne encoding, we just want this to write for all Unicode.
- dos.write(fileName.getBytes("UTF-8"));
- dos.writeBytes(midParams);
-
- // create a buffer of maximum size
- bytesAvailable = fileInputStream.available();
- bufferSize = Math.min(bytesAvailable, maxBufferSize);
- buffer = new byte[bufferSize];
-
- // read file and write it into form...
- bytesRead = fileInputStream.read(buffer, 0, bufferSize);
- totalBytes = 0;
-
- while (bytesRead > 0) {
- totalBytes += bytesRead;
- result.setBytesSent(totalBytes);
- dos.write(buffer, 0, bufferSize);
- bytesAvailable = fileInputStream.available();
- bufferSize = Math.min(bytesAvailable, maxBufferSize);
- bytesRead = fileInputStream.read(buffer, 0, bufferSize);
- }
-
- // send multipart form data necesssary after file data...
- dos.writeBytes(tailParams);
-
- // close streams
- fileInputStream.close();
- dos.flush();
- dos.close();
-
- //------------------ read the SERVER RESPONSE
- StringBuffer responseString = new StringBuffer("");
- DataInputStream inStream;
- try {
- inStream = new DataInputStream ( conn.getInputStream() );
- } catch(FileNotFoundException e) {
- throw new IOException("Received error from server");
- }
-
- String line;
- while (( line = inStream.readLine()) != null) {
- responseString.append(line);
- }
- Log.d(LOG_TAG, "got response from server");
- Log.d(LOG_TAG, responseString.toString());
-
- // send request and retrieve response
- result.setResponseCode(conn.getResponseCode());
- result.setResponse(responseString.toString());
-
- inStream.close();
- conn.disconnect();
-
- // Revert back to the proper verifier and socket factories
- if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
- ((HttpsURLConnection)conn).setHostnameVerifier(defaultHostnameVerifier);
- HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
- }
-
- return result;
- }
/**
* Downloads a file form a given URL and saves it to the specified directory.
@@ -408,7 +436,10 @@ public class FileTransfer extends Plugin {
* @param target Full path of the file on the file system
* @return JSONObject the downloaded file
*/
- public JSONObject download(String source, String target) throws IOException {
+ private PluginResult download(String source, String target) {
+ Log.d(LOG_TAG, "download " + source + " to " + target);
+
+ HttpURLConnection connection = null;
try {
File file = getFileFromPath(target);
@@ -419,7 +450,7 @@ public class FileTransfer extends Plugin {
if(this.ctx.isUrlWhiteListed(source))
{
URL url = new URL(source);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//Add cookie support
@@ -431,7 +462,7 @@ public class FileTransfer extends Plugin {
connection.connect();
- Log.d(LOG_TAG, "Download file:" + url);
+ Log.d(LOG_TAG, "Download file: " + url);
InputStream inputStream = connection.getInputStream();
byte[] buffer = new byte[1024];
@@ -450,23 +481,40 @@ public class FileTransfer extends Plugin {
// create FileEntry object
FileUtils fileUtil = new FileUtils();
+ JSONObject fileEntry = fileUtil.getEntry(file);
- return fileUtil.getEntry(file);
+ return new PluginResult(PluginResult.Status.OK, fileEntry);
}
else
{
- throw new IOException("Error: Unable to connect to domain");
+ Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ }
+
+ } catch (FileNotFoundException e) {
+ JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (MalformedURLException e) {
+ JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } catch (Exception e) { // IOException, JSONException, NullPointer
+ JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
+ Log.e(LOG_TAG, error.toString(), e);
+ return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
}
- } catch (Exception e) {
- Log.d(LOG_TAG, e.getMessage(), e);
- throw new IOException("Error while downloading");
}
}
/**
* Get an input stream based on file path or content:// uri
*
- * @param path
+ * @param path foo
* @return an input stream
* @throws FileNotFoundException
*/
@@ -491,14 +539,23 @@ public class FileTransfer extends Plugin {
/**
* Get a File object from the passed in path
*
- * @param path
- * @return
+ * @param path file path
+ * @return file object
*/
- private File getFileFromPath(String path) {
- if (path.startsWith("file://")) {
- return new File(path.substring(7));
+ private File getFileFromPath(String path) throws FileNotFoundException {
+ File file;
+ String prefix = "file://";
+
+ if (path.startsWith(prefix)) {
+ file = new File(path.substring(prefix.length()));
} else {
- return new File(path);
+ file = new File(path);
}
+
+ if (file.getParent() == null) {
+ throw new FileNotFoundException();
+ }
+
+ return file;
}
}