Skip to content

Commit 22675b4

Browse files
fix: prevent file descriptor leaks by replacing direct fs.readFileSync usage
Moved file reading logic to a new FileHelper (app/back-end/helpers/file.js) using openSync, readSync, and closeSync to ensure file descriptors are properly managed. Replaced all occurrences of fs.readFileSync with the new helper. Branch: fix-readFileSync-fd-leak
1 parent 75626e6 commit 22675b4

38 files changed

+160
-104
lines changed

app/back-end/app.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const Plugins = require('./plugins.js');
2424
const DBUtils = require('./helpers/db.utils.js');
2525
const Site = require('./site.js');
2626
const Utils = require('./helpers/utils.js');
27+
const FileHelper = require('./helpers/file.js');
2728
// List of the Event classes
2829
const EventClasses = require('./events/_modules.js');
2930
// Migration classes
@@ -52,7 +53,7 @@ class App {
5253
this.initPath = path.join(this.appDir, 'config', 'window-config.json');
5354
this.appConfigPath = path.join(this.appDir, 'config', 'app-config.json');
5455
this.tinymceOverridedConfigPath = path.join(this.appDir, 'config', 'tinymce.override.json');
55-
this.versionData = JSON.parse(fs.readFileSync(__dirname + '/builddata.json', 'utf8'));
56+
this.versionData = JSON.parse(FileHelper.readFileSync(__dirname + '/builddata.json', 'utf8'));
5657
this.windowBounds = null;
5758
this.appConfig = null;
5859
this.tinymceOverridedConfig = {};
@@ -165,8 +166,8 @@ class App {
165166

166167
// Check if both config.json files exists
167168
if (fs.existsSync(appThemeConfig) && fs.existsSync(userThemeConfig)) {
168-
let appThemeData = JSON.parse(fs.readFileSync(appThemeConfig, 'utf8'));
169-
let userThemeData = JSON.parse(fs.readFileSync(userThemeConfig, 'utf8'));
169+
let appThemeData = JSON.parse(FileHelper.readFileSync(appThemeConfig, 'utf8'));
170+
let userThemeData = JSON.parse(FileHelper.readFileSync(userThemeConfig, 'utf8'));
170171

171172
// If app theme is newer version than the existing one
172173
if(compare(appThemeData.version, userThemeData.version) === 1) {
@@ -231,7 +232,7 @@ class App {
231232
let themeDir = path.join(siteDir, 'input', 'themes', themes.currentTheme(true));
232233
let themeOverridesDir = path.join(siteDir, 'input', 'themes', themes.currentTheme(true) + '-override');
233234
let themeConfig = Themes.loadThemeConfig(themeConfigPath, themeDir);
234-
let menuStructure = fs.readFileSync(menuConfigPath, 'utf8');
235+
let menuStructure = FileHelper.readFileSync(menuConfigPath, 'utf8');
235236
let parsedMenuStructure = {};
236237

237238
try {
@@ -283,7 +284,7 @@ class App {
283284

284285
// Load the config
285286
let defaultSiteConfig = JSON.parse(JSON.stringify(defaultAstCurrentSiteConfig));
286-
let siteConfig = fs.readFileSync(configFilePath);
287+
let siteConfig = FileHelper.readFileSync(configFilePath);
287288
siteConfig = JSON.parse(siteConfig);
288289

289290
if (siteConfig.name !== siteName) {
@@ -437,7 +438,7 @@ class App {
437438
loadConfig () {
438439
// Try to get window bounds
439440
try {
440-
this.windowBounds = JSON.parse(fs.readFileSync(this.initPath, 'utf8'));
441+
this.windowBounds = JSON.parse(FileHelper.readFileSync(this.initPath, 'utf8'));
441442
} catch (e) {
442443
console.log('The window-config.json file will be created');
443444
}
@@ -490,7 +491,7 @@ class App {
490491

491492
// Try to get application config
492493
try {
493-
this.appConfig = JSON.parse(fs.readFileSync(this.appConfigPath, 'utf8'));
494+
this.appConfig = JSON.parse(FileHelper.readFileSync(this.appConfigPath, 'utf8'));
494495
this.appConfig = Utils.mergeObjects(JSON.parse(JSON.stringify(defaultAstAppConfig)), this.appConfig);
495496
} catch (e) {
496497
if (this.hasPermissionsErrors(e)) {
@@ -518,7 +519,7 @@ class App {
518519
loadAdditionalConfig () {
519520
// Try to get TinyMCE overrided config
520521
try {
521-
this.tinymceOverridedConfig = JSON.parse(fs.readFileSync(this.tinymceOverridedConfigPath, 'utf8'));
522+
this.tinymceOverridedConfig = JSON.parse(FileHelper.readFileSync(this.tinymceOverridedConfigPath, 'utf8'));
522523
} catch (e) {}
523524

524525
if (this.appConfig.sitesLocation) {

app/back-end/author.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const slug = require('./helpers/slug');
88
const ImageHelper = require('./helpers/image.helper.js');
99
const Themes = require('./themes.js');
1010
const Utils = require('./helpers/utils.js');
11+
const FileHelper = require('./helpers/file.js');
1112

1213
/**
1314
* Author Model - used for operations connected with author management
@@ -366,7 +367,7 @@ class Author extends Model {
366367
let themeConfigPath = path.join(this.application.sitesDir, this.site, 'input', 'config', 'theme.config.json');
367368

368369
if (fs.existsSync(themeConfigPath)) {
369-
let themeConfigString = fs.readFileSync(themeConfigPath, 'utf8');
370+
let themeConfigString = FileHelper.readFileSync(themeConfigPath, 'utf8');
370371
themesHelper.checkAndCleanImages(themeConfigString);
371372
}
372373
}

app/back-end/events/app.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs-extra');
22
const path = require('path');
3+
const FileHelper = require('../helpers/file.js');
34
const ipcMain = require('electron').ipcMain;
45
const Themes = require('../themes.js');
56
const Languages = require('../languages.js');
@@ -97,7 +98,7 @@ class AppEvents {
9798
* Save app color theme config
9899
*/
99100
ipcMain.on('app-save-color-theme', function (event, theme) {
100-
let appConfig = fs.readFileSync(appInstance.appConfigPath, 'utf8');
101+
let appConfig = FileHelper.readFileSync(appInstance.appConfigPath, 'utf8');
101102

102103
try {
103104
appConfig = JSON.parse(appConfig);
@@ -440,7 +441,7 @@ class AppEvents {
440441
}
441442

442443
let filePath = path.join(appInstance.app.getPath('logs'), filename);
443-
let fileContent = fs.readFileSync(filePath, 'utf8');
444+
let fileContent = FileHelper.readFileSync(filePath, 'utf8');
444445

445446
event.sender.send('app-log-file-loaded', {
446447
fileContent: fileContent
@@ -458,7 +459,7 @@ class AppEvents {
458459
return;
459460
}
460461

461-
let appConfig = fs.readFileSync(appInstance.appConfigPath, 'utf8');
462+
let appConfig = FileHelper.readFileSync(appInstance.appConfigPath, 'utf8');
462463

463464
try {
464465
appConfig = JSON.parse(appConfig);

app/back-end/events/credits.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require('fs-extra');
22
const path = require('path');
33
const ipcMain = require('electron').ipcMain;
4+
const FileHelper = require('../helpers/file.js');
45

56
/*
67
* Events for the IPC communication regarding credits
@@ -18,7 +19,7 @@ class CreditsEvents {
1819
}
1920

2021
if(fs.existsSync(filePath)) {
21-
licenseText = fs.readFileSync(filePath, 'utf-8');
22+
licenseText = FileHelper.readFileSync(filePath, 'utf-8');
2223
}
2324

2425
event.sender.send('app-license-loaded', licenseText);

app/back-end/events/page.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs');
22
const path = require('path');
3+
const FileHelper = require('../helpers/file.js');
34
const ipcMain = require('electron').ipcMain;
45
const Page = require('../page.js');
56

@@ -85,7 +86,7 @@ class PageEvents {
8586
let pagesFile = path.join(this.app.sitesDir, siteName, 'input', 'config', 'pages.config.json');
8687

8788
if (fs.existsSync(pagesFile)) {
88-
let pagesHierarchy = JSON.parse(fs.readFileSync(pagesFile, { encoding: 'utf8' }));
89+
let pagesHierarchy = JSON.parse(FileHelper.readFileSync(pagesFile, { encoding: 'utf8' }));
8990
pagesHierarchy = this.removeDuplicatedDataFromHierarchy(pagesHierarchy);
9091
event.sender.send('app-pages-hierarchy-loaded', pagesHierarchy);
9192
} else {
@@ -104,7 +105,7 @@ class PageEvents {
104105
// Update pages hierarchy during post conversion
105106
ipcMain.on('app-pages-hierarchy-update', (event, postIDs) => {
106107
let pagesFile = path.join(this.app.sitesDir, pagesData.siteName, 'input', 'config', 'pages.config.json');
107-
let pagesHierarchy = JSON.parse(fs.readFileSync(pagesFile, { encoding: 'utf8' }));
108+
let pagesHierarchy = JSON.parse(FileHelper.readFileSync(pagesFile, { encoding: 'utf8' }));
108109

109110
for (let i = 0; i < postIDs.length; i++) {
110111
pagesHierarchy.push({

app/back-end/events/plugins-api.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const ipcMain = require('electron').ipcMain;
22
const fs = require('fs');
33
const path = require('path');
4+
const FileHelper = require('../helpers/file.js');
45

56
/*
67
* Events for the IPC communication regarding plugins
@@ -19,7 +20,7 @@ class PluginsApiEvents {
1920
return false;
2021
}
2122

22-
let fileContent = fs.readFileSync(filePath);
23+
let fileContent = FileHelper.readFileSync(filePath);
2324
fileContent = fileContent.toString();
2425
return fileContent;
2526
});
@@ -34,7 +35,7 @@ class PluginsApiEvents {
3435
return false;
3536
}
3637

37-
let fileContent = fs.readFileSync(filePath);
38+
let fileContent = FileHelper.readFileSync(filePath);
3839
fileContent = fileContent.toString();
3940
return fileContent;
4041
});
@@ -50,7 +51,7 @@ class PluginsApiEvents {
5051
return false;
5152
}
5253

53-
let fileContent = fs.readFileSync(filePath);
54+
let fileContent = FileHelper.readFileSync(filePath);
5455
fileContent = fileContent.toString();
5556
return fileContent;
5657
});

app/back-end/events/site.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require('fs-extra');
22
const os = require('os');
33
const path = require('path');
4+
const FileHelper = require('../helpers/file.js');
45
const slug = require('./../helpers/slug');
56
const passwordSafeStorage = require('keytar');
67
const ipcMain = require('electron').ipcMain;
@@ -130,7 +131,7 @@ class SiteEvents {
130131
}
131132

132133
let configFile = path.join(appInstance.sitesDir, siteName, 'input', 'config', 'site.config.json');
133-
let oldConfig = fs.readFileSync(configFile, 'utf8');
134+
let oldConfig = FileHelper.readFileSync(configFile, 'utf8');
134135
let themesPath = path.join(appInstance.sitesDir, siteName, 'input', 'themes');
135136
let newThemeConfig = {};
136137
oldConfig = JSON.parse(oldConfig);
@@ -340,7 +341,7 @@ class SiteEvents {
340341
let themeConfigPath = path.join(appInstance.sitesDir, siteName, 'input', 'config', 'theme.config.json');
341342

342343
if (fs.existsSync(themeConfigPath)) {
343-
let themeConfigString = fs.readFileSync(themeConfigPath, 'utf8');
344+
let themeConfigString = FileHelper.readFileSync(themeConfigPath, 'utf8');
344345
themesHelper.checkAndCleanImages(themeConfigString);
345346
}
346347

@@ -604,7 +605,7 @@ class SiteEvents {
604605
}
605606

606607
let configPath = path.join(appInstance.sitesDir, siteName, 'input', 'config', 'site.config.json');
607-
let config = fs.readFileSync(configPath, 'utf8');
608+
let config = FileHelper.readFileSync(configPath, 'utf8');
608609

609610
try {
610611
config = JSON.parse(config);

app/back-end/events/sync.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs-extra');
22
const path = require('path');
3+
const FileHelper = require('../helpers/file.js');
34
const ipcMain = require('electron').ipcMain;
45

56
/*
@@ -23,7 +24,7 @@ class SyncEvents {
2324

2425
saveSyncStatus(status, siteName) {
2526
let configFile = path.join(this.app.sitesDir, siteName, 'input', 'config', 'site.config.json');
26-
let configContent = fs.readFileSync(configFile, 'utf8');
27+
let configContent = FileHelper.readFileSync(configFile, 'utf8');
2728
configContent = JSON.parse(configContent);
2829
configContent.synced = status;
2930

app/back-end/helpers/file.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const fs = require('fs');
2+
3+
/**
4+
* FileHelper wraps fs.readFileSync to avoid leaking file descriptors.
5+
*/
6+
class FileHelper {
7+
/**
8+
* Reads a file synchronously, ensuring the file descriptor is closed.
9+
* @param {string|Buffer|URL|integer} path - filename or file descriptor
10+
* @param {Object|string} [options] - options or encoding
11+
* @returns {string|Buffer}
12+
*/
13+
static readFileSync(path, options) {
14+
let fd;
15+
try {
16+
fd = fs.openSync(path, 'r');
17+
return fs.readFileSync(fd, options);
18+
} finally {
19+
if (fd !== undefined) {
20+
try { fs.closeSync(fd); } catch (e) { /* ignore */ }
21+
}
22+
}
23+
}
24+
}
25+
26+
module.exports = FileHelper;

app/back-end/helpers/updates.helper.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const fs = require('fs');
2+
const FileHelper = require('./file.js');
23
const https = require('https');
34

45
class UpdatesHelper {
@@ -44,7 +45,7 @@ class UpdatesHelper {
4445

4546
readExistingData () {
4647
if (fs.existsSync(this.filePath)) {
47-
let body = fs.readFileSync(this.filePath, 'utf8');
48+
let body = FileHelper.readFileSync(this.filePath, 'utf8');
4849
this.handleResponse(body);
4950
} else {
5051
this.sendError();

0 commit comments

Comments
 (0)