diff --git a/lib/extension/externalJS.ts b/lib/extension/externalJS.ts index 119a18c9..528ce7c4 100644 --- a/lib/extension/externalJS.ts +++ b/lib/extension/externalJS.ts @@ -176,7 +176,13 @@ export default abstract class ExternalJSExtension extends Extension { } const {name, code} = message; + + if (!name.endsWith(".mjs") && !name.endsWith(".js") && !name.endsWith(".cjs")) { + return utils.getResponse(message, {}, "JavaScript file must have '.mjs', '.js' or '.cjs' extension"); + } + const filePath = this.getFilePath(name, true); + try { fs.writeFileSync(filePath, code, "utf8"); this.symlinkNodeModulesIfNecessary(); diff --git a/test/extensions/externalConverters.test.ts b/test/extensions/externalConverters.test.ts index 7e733349..4406779c 100644 --- a/test/extensions/externalConverters.test.ts +++ b/test/extensions/externalConverters.test.ts @@ -488,6 +488,25 @@ describe("Extension: ExternalConverters", () => { expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/converters", stringify([]), {retain: true}); }); + it("returns error on invalid name", async () => { + const converterName = "foo1"; + + await resetExtension(); + for (const mock of mocksClear) mock.mockClear(); + + await (controller.getExtension("ExternalConverters")! as ExternalConverters).onMQTTMessage({ + topic: "zigbee2mqtt/bridge/request/converter/save", + message: {name: converterName, code: "a"}, + }); + + expect(mockMQTTPublishAsync).toHaveBeenCalledWith( + "zigbee2mqtt/bridge/response/converter/save", + expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`), + {}, + ); + expect(writeFileSyncSpy).toHaveBeenCalledTimes(0); + }); + it("returns error on invalid code", async () => { const converterName = "foo1.js"; const converterCode = "definetly not a correct javascript code"; diff --git a/test/extensions/externalExtensions.test.ts b/test/extensions/externalExtensions.test.ts index db72dc99..983cf0b0 100644 --- a/test/extensions/externalExtensions.test.ts +++ b/test/extensions/externalExtensions.test.ts @@ -299,6 +299,25 @@ describe("Extension: ExternalExtensions", () => { expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/extensions", stringify([]), {retain: true}); }); + it("returns error on invalid name", async () => { + const extensionName = "foo1"; + + await resetExtension(); + for (const mock of mocksClear) mock.mockClear(); + + await (controller.getExtension("ExternalExtensions")! as ExternalExtensions).onMQTTMessage({ + topic: "zigbee2mqtt/bridge/request/extension/save", + message: {name: extensionName, code: "a"}, + }); + + expect(mockMQTTPublishAsync).toHaveBeenCalledWith( + "zigbee2mqtt/bridge/response/extension/save", + expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`), + {}, + ); + expect(writeFileSyncSpy).toHaveBeenCalledTimes(0); + }); + it("returns error on invalid code", async () => { const extensionName = "foo1.js"; const extensionCode = "definetly not a correct javascript code";