Compare commits

..

12 Commits

Author SHA1 Message Date
Ian Clelland
6201a6f6ec Add guard lines to gradle build file for extensions 2014-11-06 14:39:58 -05:00
Ian Clelland
fc63f66e89 CB-7758: Allow content-url-hosted pages to access the bridge
This allows e.g. jsHybugger to create pages with access to Cordova APIs.
We restrict access to content provider URLs which are at subdomains of the application itself, ie, begin with "content://com.your.package.id."
2014-10-27 15:26:38 -04:00
Chris Alfano
832e626573 CB-7726 fix typo in gitignore: ant-built -> ant-build
github: close #131
2014-10-27 12:33:09 -04:00
Andrew Grieve
ce5d9a2ee8 gradle: Allow storeType to be set (allows using .p12 files) 2014-10-21 12:59:34 -04:00
Andrew Grieve
77c51d3ae7 gradle: Allow absolute paths to keystore files 2014-10-21 12:43:30 -04:00
Joe Bowser
53dae45430 Fixed the SecureRandom so it only returns positive values 2014-10-17 15:30:28 -07:00
Joe Bowser
16343ffe70 Undoing change to Math.random() for now, this creates a weird bug 2014-10-17 13:52:33 -07:00
Joe Bowser
b37498d5f6 Replacing Math.random() with something a little more random. 2014-10-14 10:11:09 -07:00
Vladimir Kotikov
9f41906895 CB-6511 Fixes build for android when app name contains unicode characters.
github: close #124
2014-10-07 15:24:12 -04:00
Rui Zhao
fbeb379f1b CB-7707 Added multipart PluginResult (close #125)
Corresponds to cordova-js commit: a1f866606b3
2014-10-07 15:17:56 -04:00
Andrew Grieve
2dcd50c11b CB-7714 Teach check_reqs about brew's install location for android SDK 2014-10-06 10:33:31 -04:00
Andrew Grieve
30681eb772 Fix --shared flag of create script (broke in recent gradle changes) 2014-10-04 15:14:51 -04:00
10 changed files with 192 additions and 103 deletions

View File

@@ -31,7 +31,7 @@ var isWindows = process.platform == 'win32';
function forgivingWhichSync(cmd) {
try {
return which.sync(cmd);
return fs.realpathSync(which.sync(cmd));
} catch (e) {
return '';
}
@@ -102,7 +102,7 @@ module.exports.check_java = function() {
} else {
// See if we can derive it from javac's location.
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
var maybeJavaHome = path.dirname(path.dirname(fs.realpathSync(javacPath)));
var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
} else {
@@ -179,9 +179,15 @@ module.exports.check_android = function() {
}
if (androidCmdPath && !hasAndroidHome) {
var parentDir = path.dirname(androidCmdPath);
var grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'tools') {
process.env['ANDROID_HOME'] = path.dirname(parentDir);
hasAndroidHome = true;
} else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true;
} else {
throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir);
}
}
if (hasAndroidHome && !adbInPath) {

View File

@@ -210,9 +210,11 @@ exports.createProject = function(project_path, package_name, project_name, proje
project_template_dir :
path.join(ROOT, 'bin', 'templates', 'project');
var safe_activity_name = project_name.replace(/\W/g, '');
var package_as_path = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', package_as_path);
// safe_activity_name is being hardcoded to avoid issues with unicode app name (https://issues.apache.org/jira/browse/CB-6511)
// TODO: provide option to specify activity name via CLI (proposal: https://issues.apache.org/jira/browse/CB-7231)
var safe_activity_name = 'MainActivity';
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
var target_api = check_reqs.get_target();
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
@@ -276,7 +278,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
copyBuildRules(project_path);
});
// Link it to local android install.
writeProjectProperties(project_path, target_api);
writeProjectProperties(project_path, target_api, use_shared_project);
}).then(function() {
console.log('Project successfully created.');
});

View File

@@ -180,16 +180,36 @@ def ensureValueExists(filePath, props, key) {
def addSigningProps(propsFilePath, signingConfig) {
def propsFile = file(propsFilePath)
def props = new Properties()
propsFile.withReader { reader ->
def props = new Properties()
props.load(reader)
signingConfig.keyAlias = ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword')
signingConfig.storeFile = RelativePath.parse(true, ensureValueExists(propsFilePath, props, 'storeFile')).getFile(propsFile.getParentFile())
signingConfig.storePassword = props.get('storePassword')
}
def storeFile = new File(ensureValueExists(propsFilePath, props, 'storeFile'))
if (!storeFile.isAbsolute()) {
storeFile = RelativePath.parse(true, storeFile.toString()).getFile(propsFile.getParentFile())
}
if (!storeFile.exists()) {
throw new FileNotFoundException('Keystore file does not exist: ' + storeFile.getAbsolutePath())
}
signingConfig.keyAlias = ensureValueExists(propsFilePath, props, 'keyAlias')
signingConfig.keyPassword = props.get('keyPassword')
signingConfig.storeFile = storeFile
signingConfig.storePassword = props.get('storePassword')
def storeType = props.get('storeType')
if (!storeType) {
def filename = storeFile.getName().toLowerCase();
if (filename.endsWith('.p12') || filename.endsWith('.pfx')) {
storeType = 'pkcs12'
}
}
if (storeType) {
signingConfig.storeType = storeType
}
}
// PLUGIN GRADLE EXTENSIONS START
// PLUGIN GRADLE EXTENSIONS END
if (file('build-extras.gradle').exists()) {
apply from: 'build-extras.gradle'
}

View File

@@ -5,7 +5,7 @@ local.properties
/gradlew.bat
/gradle
# Ant builds
ant-built
ant-build
ant-gen
# Eclipse builds
gen

View File

@@ -1,5 +1,5 @@
// Platform: android
// 8ca0f3b2b87e0759c5236b91c80f18438544409c
// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -263,11 +263,7 @@ var cordova = {
* Called by native code when returning successful result from an action.
*/
callbackSuccess: function(callbackId, args) {
try {
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = "+e);
}
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
},
/**
@@ -276,30 +272,34 @@ var cordova = {
callbackError: function(callbackId, args) {
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
// Derive success from status.
try {
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
},
/**
* Called by native code when returning the result from an action.
*/
callbackFromNative: function(callbackId, success, status, args, keepCallback) {
var callback = cordova.callbacks[callbackId];
if (callback) {
if (success && status == cordova.callbackStatus.OK) {
callback.success && callback.success.apply(null, args);
} else if (!success) {
callback.fail && callback.fail.apply(null, args);
}
callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
try {
var callback = cordova.callbacks[callbackId];
if (callback) {
if (isSuccess && status == cordova.callbackStatus.OK) {
callback.success && callback.success.apply(null, args);
} else {
callback.fail && callback.fail.apply(null, args);
}
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
// Clear callback if not expecting any more results
if (!keepCallback) {
delete cordova.callbacks[callbackId];
}
}
}
catch (err) {
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
console && console.log && console.log(msg);
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
throw err;
}
},
addConstructor: function(func) {
channel.onCordovaReady.subscribe(function() {
@@ -1013,6 +1013,42 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
}
};
function buildPayload(payload, message) {
var payloadKind = message.charAt(0);
if (payloadKind == 's') {
payload.push(message.slice(1));
} else if (payloadKind == 't') {
payload.push(true);
} else if (payloadKind == 'f') {
payload.push(false);
} else if (payloadKind == 'N') {
payload.push(null);
} else if (payloadKind == 'n') {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload.push(arraybuffer.buffer);
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
var multipartMessages = message.slice(1);
while (multipartMessages !== "") {
var spaceIdx = multipartMessages.indexOf(' ');
var msgLen = +multipartMessages.slice(0, spaceIdx);
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
buildPayload(payload, multipartMessage);
}
} else {
payload.push(JSON.parse(message));
}
}
// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
try {
@@ -1026,32 +1062,10 @@ function processMessage(message) {
var status = +message.slice(2, spaceIdx);
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
var payloadKind = message.charAt(nextSpaceIdx + 1);
var payload;
if (payloadKind == 's') {
payload = message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 't') {
payload = true;
} else if (payloadKind == 'f') {
payload = false;
} else if (payloadKind == 'N') {
payload = null;
} else if (payloadKind == 'n') {
payload = +message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 'A') {
var data = message.slice(nextSpaceIdx + 2);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload = arraybuffer.buffer;
} else if (payloadKind == 'S') {
payload = window.atob(message.slice(nextSpaceIdx + 2));
} else {
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
}
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
var payloadMessage = message.slice(nextSpaceIdx + 1);
var payload = [];
buildPayload(payload, payloadMessage);
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
} else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
}

View File

@@ -18,6 +18,8 @@
*/
package org.apache.cordova;
import java.security.SecureRandom;
import org.apache.cordova.PluginManager;
import org.json.JSONArray;
import org.json.JSONException;
@@ -35,12 +37,14 @@ public class CordovaBridge {
private NativeToJsMessageQueue jsMessageQueue;
private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
private String loadedUrl;
private String appContentUrlPrefix;
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue, String packageName) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
this.appContentUrlPrefix = "content://" + packageName + ".";
}
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
if (!verifySecret("exec()", bridgeSecret)) {
return null;
@@ -107,7 +111,8 @@ public class CordovaBridge {
/** Called by cordova.js to initialize the bridge. */
int generateBridgeSecret() {
expectedBridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
SecureRandom randGen = new SecureRandom();
expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
return expectedBridgeSecret;
}
@@ -162,7 +167,9 @@ public class CordovaBridge {
// Protect against random iframes being able to talk through the bridge.
// Trust only file URLs and the start URL's domain.
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
if (origin.startsWith("file:") || (origin.startsWith("http") && loadedUrl.startsWith(origin))) {
if (origin.startsWith("file:") ||
origin.startsWith(this.appContentUrlPrefix) ||
(origin.startsWith("http") && loadedUrl.startsWith(origin))) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
jsMessageQueue.setBridgeMode(bridgeMode);

View File

@@ -152,7 +152,7 @@ public class CordovaWebView extends WebView {
super.setWebViewClient(webViewClient);
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName());
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
pluginManager.addService("App", "org.apache.cordova.App");

View File

@@ -37,6 +37,7 @@ public class NativeToJsMessageQueue {
// Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking).
// Doesn't work for multipart messages.
private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable sending back native->JS messages during an exec() when the active
@@ -419,53 +420,43 @@ public class NativeToJsMessageQueue {
this.pluginResult = pluginResult;
}
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
return 1;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_STRING: // s
return 1 + pluginResult.getStrMessage().length();
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_MULTIPART:
int ret = 1;
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
int argLength = String.valueOf(length).length();
ret += argLength + 1 + length;
}
return ret;
case PluginResult.MESSAGE_TYPE_JSON:
default:
return pluginResult.getMessage().length();
}
}
int calculateEncodedLength() {
if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1;
}
int statusLen = String.valueOf(pluginResult.getStatus()).length();
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
ret += 1;
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
ret += 1 + pluginResult.getStrMessage().length();
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
ret += pluginResult.getMessage().length();
return ret + calculateEncodedLengthHelper(pluginResult);
}
return ret;
}
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN:
sb.append(pluginResult.getMessage().charAt(0)); // t or f.
@@ -489,12 +480,42 @@ public class NativeToJsMessageQueue {
sb.append('A');
sb.append(pluginResult.getMessage());
break;
case PluginResult.MESSAGE_TYPE_MULTIPART:
sb.append('M');
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
sb.append(' ');
encodeAsMessageHelper(sb, multipartMessage);
}
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
sb.append(pluginResult.getMessage()); // [ or {
}
}
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
encodeAsMessageHelper(sb, pluginResult);
}
void encodeAsJsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append(jsPayloadOrCallbackId);

19
framework/src/org/apache/cordova/PluginResult.java Executable file → Normal file
View File

@@ -18,6 +18,8 @@
*/
package org.apache.cordova;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -29,6 +31,7 @@ public class PluginResult {
private boolean keepCallback = false;
private String strMessage;
private String encodedMessage;
private List<PluginResult> multipartMessages;
public PluginResult(Status status) {
this(status, PluginResult.StatusMessages[status.ordinal()]);
@@ -80,6 +83,13 @@ public class PluginResult {
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
}
// The keepCallback and status of multipartMessages are ignored.
public PluginResult(Status status, List<PluginResult> multipartMessages) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_MULTIPART;
this.multipartMessages = multipartMessages;
}
public void setKeepCallback(boolean b) {
this.keepCallback = b;
}
@@ -99,6 +109,14 @@ public class PluginResult {
return encodedMessage;
}
public int getMultipartMessagesSize() {
return multipartMessages.size();
}
public PluginResult getMultipartMessage(int index) {
return multipartMessages.get(index);
}
/**
* If messageType == MESSAGE_TYPE_STRING, then returns the message string.
* Otherwise, returns null.
@@ -150,6 +168,7 @@ public class PluginResult {
// Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static final int MESSAGE_TYPE_MULTIPART = 8;
public static String[] StatusMessages = new String[] {
"No result",

View File

@@ -14,7 +14,7 @@
],
"scripts": {
"test": "jasmine-node --color spec",
"test-build": "rm -rf \"test create\"; ./bin/create \"test create\" com.test.app Test && \"./test create/cordova/build\" && rm -rf \"test create\""
"test-build": "rm -rf \"test create\"; ./bin/create \"test create\" com.test.app 応用 && \"./test create/cordova/build\" && rm -rf \"test create\""
},
"author": "Apache Software Foundation",
"license": "Apache version 2.0",