Improve plugin file handling and cleanup logic (#7434)

* Improve plugin file handling and cleanup logic Adds checks and fallbacks for temporary directory write access when creating plugin zip files, and enhances error handling when creating write streams. Updates plugin removal to use fs.rmSync with force and recursive options, and adds error handling for directory removal. * Update pluginHandler.js

TheDevRyan committed Nov 17, 2025 at 10:33 UTC afe0aed976f5eeaff4cbd7e42947315c45314a80
1 file changed +34 -4
pluginHandler.js
+34 -4
@@ -384,10 +384,34 @@ module.exports.pluginHandler = function (parent) {
384 parent.db.getPlugin(id, function (err, docs) {
385 // the "id" would probably suffice, but is probably an sanitary issue, generate a random instead
386 var randId = Math.random().toString(32).replace('0.', '');
387 - var fileName = obj.parent.path.join(require('os').tmpdir(), 'Plugin_' + randId + '.zip');
387 + var tmpDir = require('os').tmpdir();
388 + var fileName = obj.parent.path.join(tmpDir, 'Plugin_' + randId + '.zip');
389 + try {
390 + obj.fs.accessSync(tmpDir, obj.fs.constants.W_OK);
391 + } catch (e) {
392 + var pluginTmpPath = obj.parent.path.join(obj.pluginPath, '_tmp');
393 + if (!obj.fs.existsSync(pluginTmpPath)) {
394 + obj.fs.mkdirSync(pluginTmpPath, { recursive: true });
395 + }
396 + fileName = obj.parent.path.join(pluginTmpPath, 'Plugin_' + randId + '.zip');
397 + }
398 var plugin = docs[0];
399 if (plugin.repository.type == 'git') {
390 - const file = obj.fs.createWriteStream(fileName);
400 + var file;
401 + try {
402 + file = obj.fs.createWriteStream(fileName);
403 + } catch (e) {
404 + if (fileName.indexOf(tmpDir) >= 0) {
405 + var pluginTmpPath = obj.parent.path.join(obj.pluginPath, '_tmp');
406 + if (!obj.fs.existsSync(pluginTmpPath)) {
407 + obj.fs.mkdirSync(pluginTmpPath, { recursive: true });
408 + }
409 + fileName = obj.parent.path.join(pluginTmpPath, 'Plugin_' + randId + '.zip');
410 + file = obj.fs.createWriteStream(fileName);
411 + } else {
412 + throw e;
413 + }
414 + }
415 var dl_url = plugin.downloadUrl;
416 if (version_only != null && version_only != false) dl_url = version_only.url;
417 if (force_url != null) dl_url = force_url;
@@ -411,7 +435,7 @@ module.exports.pluginHandler = function (parent) {
435 var request = http.get(opts, function (response) {
436 // handle redirections with grace
437 if (response.headers.location) {
414 - file.close(function () { obj.fs.unlink(file.path, function(err) { void err; }); });
438 + file.close(() => obj.fs.unlink(fileName, () => {}));
439 return obj.installPlugin(id, version_only, response.headers.location, func);
440 }
441 response.pipe(file);
@@ -544,7 +568,13 @@ module.exports.pluginHandler = function (parent) {
568 parent.db.getPlugin(id, function (err, docs) {
569 var plugin = docs[0];
570 let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
547 - obj.fs.rmdirSync(pluginPath, { recursive: true });
571 + if (obj.fs.existsSync(pluginPath)) {
572 + try {
573 + obj.fs.rmSync(pluginPath, { recursive: true, force: true });
574 + } catch (e) {
575 + console.log("Error removing plugin directory:", e);
576 + }
577 + }
578 parent.db.deletePlugin(id, func);
579 delete obj.plugins[plugin.shortName];
580 });