--- a/toolkit/components/extensions/NativeManifests.jsm
+++ b/toolkit/components/extensions/NativeManifests.jsm
@@ -13,113 +13,128 @@ Cu.import("resource://gre/modules/XPCOMU
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
OS: "resource://gre/modules/osfile.jsm",
Schemas: "resource://gre/modules/Schemas.jsm",
Services: "resource://gre/modules/Services.jsm",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
});
-const HOST_MANIFEST_SCHEMA = "chrome://extensions/content/schemas/native_host_manifest.json";
-const VALID_APPLICATION = /^\w+(\.\w+)*$/;
+const DASHED = AppConstants.platform === "linux";
-const REGPATH = "Software\\Mozilla\\NativeMessagingHosts";
+// Supported native manifest types, with platform-specific slugs.
+const TYPES = {
+ stdio: DASHED ? "native-messaging-hosts" : "NativeMessagingHosts",
+ storage: DASHED ? "managed-storage" : "ManagedStorage",
+ pkcs11: DASHED ? "pkcs11-modules" : "PKCS11Modules",
+};
+
+const NATIVE_MANIFEST_SCHEMA = "chrome://extensions/content/schemas/native_manifest.json";
+
+const REGPATH = "Software\\Mozilla";
this.NativeManifests = {
_initializePromise: null,
_lookup: null,
init() {
if (!this._initializePromise) {
let platform = AppConstants.platform;
if (platform == "win") {
this._lookup = this._winLookup;
} else if (platform == "macosx" || platform == "linux") {
let dirs = [
Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile).path,
Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile).path,
];
- this._lookup = (application, context) => this._tryPaths(application, dirs, context);
+ this._lookup = (type, name, context) => this._tryPaths(type, name, dirs, context);
} else {
- throw new Error(`Native messaging is not supported on ${AppConstants.platform}`);
+ throw new Error(`Native manifests are not supported on ${AppConstants.platform}`);
}
- this._initializePromise = Schemas.load(HOST_MANIFEST_SCHEMA);
+ this._initializePromise = Schemas.load(NATIVE_MANIFEST_SCHEMA);
}
return this._initializePromise;
},
- _winLookup(application, context) {
+ _winLookup(type, name, context) {
const REGISTRY = Ci.nsIWindowsRegKey;
- let regPath = `${REGPATH}\\${application}`;
+ let regPath = `${REGPATH}\\${TYPES[type]}\\${name}`;
let path = WindowsRegistry.readRegKey(REGISTRY.ROOT_KEY_CURRENT_USER,
regPath, "", REGISTRY.WOW64_64);
if (!path) {
path = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
regPath, "", REGISTRY.WOW64_64);
}
if (!path) {
return null;
}
- return this._tryPath(path, application, context)
+ return this._tryPath(type, path, name, context)
.then(manifest => manifest ? {path, manifest} : null);
},
- _tryPath(path, application, context) {
+ _tryPath(type, path, name, context) {
return Promise.resolve()
.then(() => OS.File.read(path, {encoding: "utf-8"}))
.then(data => {
let manifest;
try {
manifest = JSON.parse(data);
} catch (ex) {
- let msg = `Error parsing native host manifest ${path}: ${ex.message}`;
- Cu.reportError(msg);
+ Cu.reportError(`Error parsing native manifest ${path}: ${ex.message}`);
return null;
}
- let normalized = Schemas.normalize(manifest, "manifest.NativeHostManifest", context);
+ let normalized = Schemas.normalize(manifest, "manifest.NativeManifest", context);
if (normalized.error) {
Cu.reportError(normalized.error);
return null;
}
manifest = normalized.value;
- if (manifest.name != application) {
- let msg = `Native host manifest ${path} has name property ${manifest.name} (expected ${application})`;
- Cu.reportError(msg);
+
+ if (manifest.type !== type) {
+ Cu.reportError(`Native manifest ${path} has type property ${manifest.type} (expected ${type})`);
return null;
}
- return normalized.value;
+ if (manifest.name !== name) {
+ Cu.reportError(`Native manifest ${path} has name property ${manifest.name} (expected ${name})`);
+ return null;
+ }
+ if (manifest.allowed_extensions &&
+ !manifest.allowed_extensions.includes(context.extension.id)) {
+ Cu.reportError(`This extension does not have permission to use native manifest ${path}`);
+ return null;
+ }
+
+ return manifest;
}).catch(ex => {
if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
return null;
}
throw ex;
});
},
- async _tryPaths(application, dirs, context) {
+ async _tryPaths(type, name, dirs, context) {
for (let dir of dirs) {
- let path = OS.Path.join(dir, `${application}.json`);
- let manifest = await this._tryPath(path, application, context);
+ let path = OS.Path.join(dir, TYPES[type], `${name}.json`);
+ let manifest = await this._tryPath(type, path, name, context);
if (manifest) {
return {path, manifest};
}
}
return null;
},
/**
- * Search for a valid native host manifest for the given application name.
+ * Search for a valid native manifest of the given type and name.
* The directories searched and rules for manifest validation are all
- * detailed in the native messaging documentation.
+ * detailed in the Native Manifests documentation.
*
- * @param {string} application The name of the applciation to search for.
+ * @param {string} type The type, one of: "pkcs11", "stdio" or "storage".
+ * @param {string} name The name of the manifest to search for.
* @param {object} context A context object as expected by Schemas.normalize.
* @returns {object} The contents of the validated manifest, or null if
- * no valid manifest can be found for this application.
+ * no valid manifest can be found for this type and name.
*/
- lookupApplication(application, context) {
- if (!VALID_APPLICATION.test(application)) {
- throw new Error(`Invalid application "${application}"`);
- }
- return this.init().then(() => this._lookup(application, context));
+ lookupManifest(type, name, context) {
+ return this.init().then(() => this._lookup(type, name, context));
},
};
--- a/toolkit/components/extensions/NativeMessaging.jsm
+++ b/toolkit/components/extensions/NativeMessaging.jsm
@@ -61,22 +61,22 @@ this.NativeApp = class extends EventEmit
this.context.callOnClose(this);
this.proc = null;
this.readPromise = null;
this.sendQueue = [];
this.writePromise = null;
this.sentDisconnect = false;
- this.startupPromise = NativeManifests.lookupApplication(application, context)
+ this.startupPromise = NativeManifests.lookupManifest("stdio", application, context)
.then(hostInfo => {
- // Put the two errors together to not leak information about whether a native
+ // Report a generic error to not leak information about whether a native
// application is installed to addons that do not have the right permission.
- if (!hostInfo || !hostInfo.manifest.allowed_extensions.includes(context.extension.id)) {
- throw new context.cloneScope.Error(`This extension does not have permission to use native application ${application} (or the application is not installed)`);
+ if (!hostInfo) {
+ throw new context.cloneScope.Error(`No such native application ${application}`);
}
let command = hostInfo.manifest.path;
if (AppConstants.platform == "win") {
// OS.Path.join() ignores anything before the last absolute path
// it sees, so if command is already absolute, it remains unchanged
// here. If it is relative, we get the proper absolute path here.
command = OS.Path.join(OS.Path.dirname(hostInfo.path), command);
--- a/toolkit/components/extensions/extensions-toolkit.manifest
+++ b/toolkit/components/extensions/extensions-toolkit.manifest
@@ -4,14 +4,14 @@ category webextension-modules toolkit ch
category webextension-scripts a-toolkit chrome://extensions/content/ext-toolkit.js
category webextension-scripts b-tabs-base chrome://extensions/content/ext-tabs-base.js
category webextension-scripts-content toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-devtools toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-scripts-addon toolkit chrome://extensions/content/ext-c-toolkit.js
category webextension-schemas events chrome://extensions/content/schemas/events.json
-category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
+category webextension-schemas native_manifest chrome://extensions/content/schemas/native_manifest.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
component {21f9819e-4cdf-49f9-85a0-850af91a5058} extension-process-script.js
contract @mozilla.org/webextensions/extension-process-script;1 {21f9819e-4cdf-49f9-85a0-850af91a5058}
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -17,17 +17,17 @@ toolkit.jar:
content/extensions/schemas/extension_protocol_handlers.json
content/extensions/schemas/i18n.json
#ifndef ANDROID
content/extensions/schemas/identity.json
#endif
content/extensions/schemas/idle.json
content/extensions/schemas/management.json
content/extensions/schemas/manifest.json
- content/extensions/schemas/native_host_manifest.json
+ content/extensions/schemas/native_manifest.json
content/extensions/schemas/notifications.json
content/extensions/schemas/permissions.json
content/extensions/schemas/proxy.json
content/extensions/schemas/privacy.json
content/extensions/schemas/runtime.json
content/extensions/schemas/storage.json
content/extensions/schemas/test.json
content/extensions/schemas/theme.json
rename from toolkit/components/extensions/schemas/native_host_manifest.json
rename to toolkit/components/extensions/schemas/native_manifest.json
--- a/toolkit/components/extensions/schemas/native_host_manifest.json
+++ b/toolkit/components/extensions/schemas/native_manifest.json
@@ -1,37 +1,61 @@
[
{
"namespace": "manifest",
"types": [
{
- "id": "NativeHostManifest",
- "type": "object",
- "description": "Represents a native host manifest file",
- "properties": {
- "name": {
- "type": "string",
- "pattern": "^\\w+(\\.\\w+)*$"
- },
- "description": {
- "type": "string"
+ "id": "NativeManifest",
+ "description": "Represents a native manifest file",
+ "choices": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^\\w+(\\.\\w+)*$"
+ },
+ "description": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "pkcs11", "stdio"
+ ]
+ },
+ "allowed_extensions": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "manifest.ExtensionID"
+ }
+ }
+ }
},
- "path": {
- "type": "string"
- },
- "type": {
- "type": "string",
- "enum": [
- "stdio"
- ]
- },
- "allowed_extensions": {
- "type": "array",
- "minItems": 1,
- "items": {
- "$ref": "manifest.ExtensionID"
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "$ref": "manifest.ExtensionID"
+ },
+ "description": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "storage"
+ ]
+ }
}
}
- }
+ ]
}
]
}
]
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -313,16 +313,17 @@
{
"name": "connectNative",
"type": "function",
"description": "Connects to a native application in the host machine.",
"permissions": ["nativeMessaging"],
"parameters": [
{
"type": "string",
+ "pattern": "^\\w+(\\.\\w+)*$",
"name": "application",
"description": "The name of the registered application to connect to."
}
],
"returns": {
"$ref": "Port",
"description": "Port through which messages can be sent and received with the application"
}
@@ -365,17 +366,18 @@
"type": "function",
"description": "Send a single message to a native application.",
"permissions": ["nativeMessaging"],
"async": "responseCallback",
"parameters": [
{
"name": "application",
"description": "The name of the native messaging host.",
- "type": "string"
+ "type": "string",
+ "pattern": "^\\w+(\\.\\w+)*$"
},
{
"name": "message",
"description": "The message that will be passed to the native messaging host.",
"type": "any"
},
{
"type": "function",
--- a/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/head_native_messaging.js
@@ -15,27 +15,29 @@ XPCOMUtils.defineLazyModuleGetter(this,
let {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm", {});
// It's important that we use a space in this directory name to make sure we
// correctly handle executing batch files with spaces in their path.
let tmpDir = FileUtils.getDir("TmpD", ["Native Messaging"]);
tmpDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+const TYPE_SLUG = AppConstants.platform === "linux" ? "native-messaging-hosts" : "NativeMessagingHosts";
+OS.File.makeDir(OS.Path.join(tmpDir.path, TYPE_SLUG));
+
do_register_cleanup(() => {
tmpDir.remove(true);
});
function getPath(filename) {
- return OS.Path.join(tmpDir.path, filename);
+ return OS.Path.join(tmpDir.path, TYPE_SLUG, filename);
}
const ID = "native@tests.mozilla.org";
-
async function setupHosts(scripts) {
const PERMS = {unixMode: 0o755};
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
const pythonPath = await Subprocess.pathSearch(env.get("PYTHON"));
async function writeManifest(script, scriptPath, path) {
let body = `#!${pythonPath} -u\n${script.script}`;
--- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
@@ -403,17 +403,17 @@ add_task(async function test_ext_permiss
// Test that an extension that is not listed in allowed_extensions for
// a native application cannot use that application.
add_task(async function test_app_permission() {
function background() {
let port = browser.runtime.connectNative("echo");
port.onDisconnect.addListener(msgPort => {
browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument");
- browser.test.assertEq("This extension does not have permission to use native application echo (or the application is not installed)", port.error && port.error.message);
+ browser.test.assertEq("No such native application echo", port.error && port.error.message);
browser.test.sendMessage("result", "disconnected");
});
port.onMessage.addListener(msg => {
browser.test.sendMessage("result", "message");
});
port.postMessage({test: "test"});
}
@@ -454,17 +454,17 @@ add_task(async function test_child_proce
});
await extension.startup();
let msg = await extension.awaitMessage("result");
equal(msg.args.length, 3, "Received two command line arguments");
equal(msg.args[1], getPath("info.json"), "Command line argument is the path to the native host manifest");
equal(msg.args[2], ID, "Second command line argument is the ID of the calling extension");
- equal(msg.cwd.replace(/^\/private\//, "/"), tmpDir.path,
+ equal(msg.cwd.replace(/^\/private\//, "/"), OS.Path.join(tmpDir.path, TYPE_SLUG),
"Working directory is the directory containing the native appliation");
let exitPromise = waitForSubprocessExit();
await extension.unload();
await exitPromise;
});
add_task(async function test_stderr() {
--- a/toolkit/components/extensions/test/xpcshell/test_native_manifests.js
+++ b/toolkit/components/extensions/test/xpcshell/test_native_manifests.js
@@ -19,27 +19,32 @@ if (AppConstants.platform == "win") {
registry.shutdown();
});
}
const REGPATH = "Software\\Mozilla\\NativeMessagingHosts";
const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
-let dir = FileUtils.getDir("TmpD", ["NativeMessaging"]);
+const TYPE_SLUG = AppConstants.platform === "linux" ? "native-messaging-hosts" : "NativeMessagingHosts";
+
+let dir = FileUtils.getDir("TmpD", ["NativeManifests"]);
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let userDir = dir.clone();
userDir.append("user");
userDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let globalDir = dir.clone();
globalDir.append("global");
globalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+OS.File.makeDir(OS.Path.join(userDir.path, TYPE_SLUG));
+OS.File.makeDir(OS.Path.join(globalDir.path, TYPE_SLUG));
+
let dirProvider = {
getFile(property) {
if (property == "XREUserNativeManifests") {
return userDir.clone();
} else if (property == "XRESysNativeManifests") {
return globalDir.clone();
}
return null;
@@ -70,16 +75,19 @@ add_task(async function setup() {
}
notEqual(PYTHON, null, "Found a suitable python interpreter");
});
let global = this;
// Test of NativeManifests.lookupApplication() begin here...
let context = {
+ extension: {
+ id: "extension@tests.mozilla.org",
+ },
url: null,
jsonStringify(...args) { return JSON.stringify(...args); },
cloneScope: global,
logError() {},
preprocessors: {},
callOnClose: () => {},
forgetOnClose: () => {},
};
@@ -104,25 +112,25 @@ let templateManifest = {
name: "test",
description: "this is only a test",
path: "/bin/cat",
type: "stdio",
allowed_extensions: ["extension@tests.mozilla.org"],
};
function lookupApplication(app, ctx) {
- return NativeManifests.lookupApplication(app, ctx);
+ return NativeManifests.lookupManifest("stdio", app, ctx);
}
add_task(async function test_nonexistent_manifest() {
let result = await lookupApplication("test", context);
equal(result, null, "lookupApplication returns null for non-existent application");
});
-const USER_TEST_JSON = OS.Path.join(userDir.path, "test.json");
+const USER_TEST_JSON = OS.Path.join(userDir.path, TYPE_SLUG, "test.json");
add_task(async function test_good_manifest() {
await writeManifest(USER_TEST_JSON, templateManifest);
if (registry) {
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
`${REGPATH}\\test`, "", USER_TEST_JSON);
}
@@ -184,17 +192,17 @@ add_task(async function test_invalid_typ
add_task(async function test_no_allowed_extensions() {
let manifest = Object.assign({}, templateManifest);
manifest.allowed_extensions = [];
await writeManifest(USER_TEST_JSON, manifest);
let result = await lookupApplication("test", context);
equal(result, null, "lookupApplication ignores manifest with no allowed_extensions");
});
-const GLOBAL_TEST_JSON = OS.Path.join(globalDir.path, "test.json");
+const GLOBAL_TEST_JSON = OS.Path.join(globalDir.path, TYPE_SLUG, "test.json");
let globalManifest = Object.assign({}, templateManifest);
globalManifest.description = "This manifest is from the systemwide directory";
add_task(async function good_manifest_system_dir() {
await OS.File.remove(USER_TEST_JSON);
await writeManifest(GLOBAL_TEST_JSON, globalManifest);
if (registry) {
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
@@ -240,31 +248,31 @@ while True:
signal.pause()
msglen = struct.unpack('@I', rawlen)[0]
msg = sys.stdin.read(msglen)
sys.stdout.write(struct.pack('@I', msglen))
sys.stdout.write(msg)
`;
- let scriptPath = OS.Path.join(userDir.path, "wontdie.py");
- let manifestPath = OS.Path.join(userDir.path, "wontdie.json");
+ let scriptPath = OS.Path.join(userDir.path, TYPE_SLUG, "wontdie.py");
+ let manifestPath = OS.Path.join(userDir.path, TYPE_SLUG, "wontdie.json");
const ID = "native@tests.mozilla.org";
let manifest = {
name: "wontdie",
description: "test async shutdown of native apps",
type: "stdio",
allowed_extensions: [ID],
};
if (AppConstants.platform == "win") {
await OS.File.writeAtomic(scriptPath, SCRIPT);
- let batPath = OS.Path.join(userDir.path, "wontdie.bat");
+ let batPath = OS.Path.join(userDir.path, TYPE_SLUG, "wontdie.bat");
let batBody = `@ECHO OFF\n${PYTHON} -u "${scriptPath}" %*\n`;
await OS.File.writeAtomic(batPath, batBody);
await OS.File.setPermissions(batPath, {unixMode: 0o755});
manifest.path = batPath;
await writeManifest(manifestPath, manifest);
registry.setValue(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,