diff --git a/bin/create.bat b/bin/create.bat index 35fdc3b6..cdbd6118 100644 --- a/bin/create.bat +++ b/bin/create.bat @@ -16,17 +16,37 @@ :: under the License. @ECHO OFF -IF NOT DEFINED JAVA_HOME GOTO MISSING +IF NOT DEFINED JAVA_HOME GOTO MISSING_JAVA_HOME + FOR %%X in (java.exe javac.exe ant.bat android.bat) do ( - SET FOUND=%%~$PATH:X - IF NOT DEFINED FOUND GOTO MISSING + IF [%%~$PATH:X]==[] ( + ECHO Cannot locate %%X using the PATH environment variable. + ECHO Retry after adding directory containing %%X to the PATH variable. + ECHO Remember to open a new command window after updating the PATH variable. + IF "%%X"=="java.exe" GOTO GET_JAVA + IF "%%X"=="javac.exe" GOTO GET_JAVA + IF "%%X"=="ant.bat" GOTO GET_ANT + IF "%%X"=="android.bat" GOTO GET_ANDROID + GOTO ERROR + ) ) cscript "%~dp0\create.js" %* GOTO END -:MISSING -ECHO Missing one of the following: -ECHO JDK: http://java.oracle.com -ECHO Android SDK: http://developer.android.com -ECHO Apache ant: http://ant.apache.org +:MISSING_JAVA_HOME + ECHO The JAVA_HOME environment variable is not set. + ECHO Set JAVA_HOME to an existing JRE directory. + ECHO Remember to also add JAVA_HOME to the PATH variable. + ECHO After updating system variables, open a new command window and retry. + GOTO ERROR +:GET_JAVA + ECHO Visit http://java.oracle.com if you need to install Java (JDK). + GOTO ERROR +:GET_ANT + ECHO Visit http://ant.apache.org if you need to install Apache Ant. + GOTO ERROR +:GET_ANDROID + ECHO Visit http://developer.android.com if you need to install the Android SDK. + GOTO ERROR +:ERROR EXIT /B 1 :END diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java index f5de38db..594c2b27 100644 --- a/framework/src/org/apache/cordova/Config.java +++ b/framework/src/org/apache/cordova/Config.java @@ -171,7 +171,7 @@ public class Config { LOG.i("CordovaLog", "Found start page location: %s", src); if (src != null) { - Pattern schemeRegex = Pattern.compile("^[a-z]+://"); + Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); Matcher matcher = schemeRegex.matcher(src); if (matcher.find()) { startUrl = src; @@ -220,19 +220,33 @@ public class Config { } else { // specific access // check if subdomains should be included // TODO: we should not add more domains if * has already been added + Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); + Matcher matcher = schemeRegex.matcher(origin); if (subdomains) { - // XXX making it stupid friendly for people who forget to include protocol/SSL + // Check for http or https protocols if (origin.startsWith("http")) { this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?"))); - } else { + } + // Check for other protocols + else if(matcher.find()){ + this.whiteList.add(Pattern.compile("^" + origin.replaceFirst("//", "//(.*\\.)?"))); + } + // XXX making it stupid friendly for people who forget to include protocol/SSL + else { this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin)); } LOG.d(TAG, "Origin to allow with subdomains: %s", origin); } else { - // XXX making it stupid friendly for people who forget to include protocol/SSL + // Check for http or https protocols if (origin.startsWith("http")) { this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://"))); - } else { + } + // Check for other protocols + else if(matcher.find()){ + this.whiteList.add(Pattern.compile("^" + origin)); + } + // XXX making it stupid friendly for people who forget to include protocol/SSL + else { this.whiteList.add(Pattern.compile("^https?://" + origin)); } LOG.d(TAG, "Origin to allow: %s", origin); diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index dba29aff..c1ca15cf 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -41,6 +41,8 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Iterator; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -99,11 +101,84 @@ public class FileTransfer extends CordovaPlugin { } } + /** + * Adds an interface method to an InputStream to return the number of bytes + * read from the raw stream. This is used to track total progress against + * the HTTP Content-Length header value from the server. + */ + private static abstract class TrackingInputStream extends FilterInputStream { + 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; + } + } + + /** + * Provides raw bytes-read tracking for a GZIP input stream. Reports the + * total number of compressed bytes read from the input, rather than the + * 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(); + } + } + + /** + * Provides simple total-bytes-read tracking for an existing InputStream + */ + private static class TrackingHTTPInputStream extends TrackingInputStream { + private long bytesRead = 0; + public TrackingHTTPInputStream(InputStream stream) { + super(stream); + } + + private int updateBytesRead(int newBytesRead) { + if (newBytesRead != -1) { + bytesRead += newBytesRead; + } + return newBytesRead; + } + + @Override + public int read() throws IOException { + return updateBytesRead(super.read()); + } + + @Override + public int read(byte[] buffer) throws IOException { + return updateBytesRead(super.read(buffer)); + } + + @Override + public int read(byte[] bytes, int offset, int count) throws IOException { + return updateBytesRead(super.read(bytes, offset, count)); + } + + public long getTotalRawBytesRead() { + return bytesRead; + } + } + /** * Works around a bug on Android 2.3. * http://code.google.com/p/android/issues/detail?id=14562 */ - private static final class DoneHandlerInputStream extends FilterInputStream { + private static final class DoneHandlerInputStream extends TrackingHTTPInputStream { private boolean done; public DoneHandlerInputStream(InputStream stream) { @@ -204,6 +279,7 @@ public class FileTransfer extends CordovaPlugin { // Look for headers on the params map for backwards compatibility with older Cordova versions. 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"); Log.d(LOG_TAG, "fileKey: " + fileKey); Log.d(LOG_TAG, "fileName: " + fileName); @@ -213,6 +289,7 @@ public class FileTransfer extends CordovaPlugin { Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); Log.d(LOG_TAG, "headers: " + headers); Log.d(LOG_TAG, "objectId: " + objectId); + Log.d(LOG_TAG, "httpMethod: " + httpMethod); final URL url; try { @@ -280,7 +357,7 @@ public class FileTransfer extends CordovaPlugin { conn.setUseCaches(false); // Use a post method. - conn.setRequestMethod("POST"); + conn.setRequestMethod(httpMethod); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); // Set the cookies on the response @@ -407,7 +484,7 @@ public class FileTransfer extends CordovaPlugin { int responseCode = conn.getResponseCode(); Log.d(LOG_TAG, "response code: " + responseCode); Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields()); - InputStream inStream = null; + TrackingInputStream inStream = null; try { inStream = getInputStream(conn); synchronized (context) { @@ -483,11 +560,15 @@ public class FileTransfer extends CordovaPlugin { } } - private static InputStream getInputStream(URLConnection conn) throws IOException { + private static TrackingInputStream getInputStream(URLConnection conn) throws IOException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return new DoneHandlerInputStream(conn.getInputStream()); } - return conn.getInputStream(); + String encoding = conn.getContentEncoding(); + if (encoding != null && encoding.equalsIgnoreCase("gzip")) { + return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream())); + } + return new TrackingHTTPInputStream(conn.getInputStream()); } // always verify the host - don't check for certificate @@ -698,6 +779,9 @@ public class FileTransfer extends CordovaPlugin { { 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) { @@ -709,14 +793,15 @@ public class FileTransfer extends CordovaPlugin { Log.d(LOG_TAG, "Download file:" + url); FileProgressResult progress = new FileProgressResult(); - if (connection.getContentEncoding() == null) { - // Only trust content-length header if no gzip etc + 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; - InputStream inputStream = null; + TrackingInputStream inputStream = null; try { inputStream = getInputStream(connection); @@ -731,12 +816,10 @@ public class FileTransfer extends CordovaPlugin { // write bytes to file byte[] buffer = new byte[MAX_BUFFER_SIZE]; int bytesRead = 0; - long totalBytes = 0; while ((bytesRead = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, bytesRead); - totalBytes += bytesRead; // Send a progress event. - progress.setLoaded(totalBytes); + progress.setLoaded(inputStream.getTotalRawBytesRead()); PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); progressResult.setKeepCallback(true); context.sendPluginResult(progressResult); diff --git a/framework/src/org/apache/cordova/InAppBrowser.java b/framework/src/org/apache/cordova/InAppBrowser.java index 48e27c60..0d5d4965 100644 --- a/framework/src/org/apache/cordova/InAppBrowser.java +++ b/framework/src/org/apache/cordova/InAppBrowser.java @@ -151,6 +151,21 @@ public class InAppBrowser extends CordovaPlugin { pluginResult.setKeepCallback(false); this.callbackContext.sendPluginResult(pluginResult); } + else if (action.equals("injectScriptCode")) { + String source = args.getString(0); + + org.json.JSONArray jsonEsc = new org.json.JSONArray(); + jsonEsc.put(source); + String jsonRepr = jsonEsc.toString(); + String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); + String scriptEnclosure = "(function(d){var c=d.createElement('script');c.type='text/javascript';c.innerText=" + + jsonSourceString + + ";d.getElementsByTagName('head')[0].appendChild(c);})(document)"; + this.inAppWebView.loadUrl("javascript:" + scriptEnclosure); + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK); + this.callbackContext.sendPluginResult(pluginResult); + } else { status = PluginResult.Status.INVALID_ACTION; } @@ -445,7 +460,7 @@ public class InAppBrowser extends CordovaPlugin { //Toggle whether this is enabled or not! Bundle appSettings = cordova.getActivity().getIntent().getExtras(); - boolean enableDatabase = appSettings.getBoolean("InAppBrowserStorageEnabled", true); + boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true); if(enableDatabase) { String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath(); diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index 337ef129..774b21c3 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -30,6 +30,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.Intent; import android.content.res.XmlResourceParser; +import android.util.Log; import android.webkit.WebResourceResponse; /** @@ -213,6 +214,7 @@ public class PluginManager { public boolean exec(String service, String action, String callbackId, String rawArgs) { CordovaPlugin plugin = this.getPlugin(service); if (plugin == null) { + Log.d(TAG, "exec() call to unknown plugin: " + service); PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); app.sendPluginResult(cr, callbackId); return true;