diff --git a/boot/boot.js b/boot/boot.js index da1c55bd0..781081f1d 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -409,6 +409,19 @@ $tw.utils.parseFields = function(text,fields) { return fields; }; +// Safely parse a string as JSON +$tw.utils.parseJSONSafe = function(text,defaultJSON) { + try { + return JSON.parse(text); + } catch(e) { + if(typeof defaultJSON === "function") { + return defaultJSON(e); + } else { + return defaultJSON || {}; + } + } +}; + /* Resolves a source filepath delimited with `/` relative to a specified absolute root filepath. In relative paths, the special folder name `..` refers to immediate parent directory, and the @@ -1322,7 +1335,7 @@ $tw.Wiki = function(options) { var tiddler = tiddlers[title]; if(tiddler) { if(tiddler.fields.type === "application/json" && tiddler.hasField("plugin-type") && tiddler.fields.text) { - pluginInfo[tiddler.fields.title] = JSON.parse(tiddler.fields.text); + pluginInfo[tiddler.fields.title] = $tw.utils.parseJSONSafe(tiddler.fields.text); results.modifiedPlugins.push(tiddler.fields.title); } } else { @@ -1455,7 +1468,7 @@ $tw.Wiki.prototype.defineTiddlerModules = function() { } break; case "application/json": - $tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],JSON.parse(tiddler.fields.text)); + $tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],$tw.utils.parseJSONSafe(tiddler.fields.text)); break; case "application/x-tiddler-dictionary": $tw.modules.define(tiddler.fields.title,tiddler.fields["module-type"],$tw.utils.parseFields(tiddler.fields.text)); @@ -1644,12 +1657,7 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/json","tiddlerdeserializer",{ } return true; }, - data = {}; - try { - data = JSON.parse(text); - } catch(e) { - // Ignore JSON parse errors - } + data = $tw.utils.parseJSONSafe(text); if($tw.utils.isArray(data) && isTiddlerArrayValid(data)) { return data; } else if(isTiddlerValid(data)) { @@ -1689,7 +1697,7 @@ $tw.boot.decryptEncryptedTiddlers = function(callback) { $tw.crypto.setPassword(data.password); var decryptedText = $tw.crypto.decrypt(encryptedText); if(decryptedText) { - var json = JSON.parse(decryptedText); + var json = $tw.utils.parseJSONSafe(decryptedText); for(var title in json) { $tw.preloadTiddler(json[title]); } @@ -1889,7 +1897,7 @@ filepath: pathname of the directory containing the specification file $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) { var tiddlers = []; // Read the specification - var filesInfo = JSON.parse(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8")); + var filesInfo = $tw.utils.parseJSONSafe(fs.readFileSync(filepath + path.sep + "tiddlywiki.files","utf8")); // Helper to process a file var processFile = function(filename,isTiddlerFile,fields,isEditableFile) { var extInfo = $tw.config.fileExtensionInfo[path.extname(filename)], @@ -2019,7 +2027,7 @@ $tw.loadPluginFolder = function(filepath,excludeRegExp) { console.log("Warning: missing plugin.info file in " + filepath); return null; } - var pluginInfo = JSON.parse(fs.readFileSync(infoPath,"utf8")); + var pluginInfo = $tw.utils.parseJSONSafe(fs.readFileSync(infoPath,"utf8")); // Read the plugin files var pluginFiles = $tw.loadTiddlersFromPath(filepath,excludeRegExp); // Save the plugin tiddlers into the plugin info @@ -2136,7 +2144,7 @@ $tw.loadWikiTiddlers = function(wikiPath,options) { pluginFields; // Bail if we don't have a wiki info file if(fs.existsSync(wikiInfoPath)) { - wikiInfo = JSON.parse(fs.readFileSync(wikiInfoPath,"utf8")); + wikiInfo = $tw.utils.parseJSONSafe(fs.readFileSync(wikiInfoPath,"utf8")); } else { return null; } diff --git a/core/modules/commands/init.js b/core/modules/commands/init.js index 51b80f267..2d053ae3c 100644 --- a/core/modules/commands/init.js +++ b/core/modules/commands/init.js @@ -48,7 +48,7 @@ Command.prototype.execute = function() { } // Tweak the tiddlywiki.info to remove any included wikis var packagePath = $tw.boot.wikiPath + "/tiddlywiki.info", - packageJson = JSON.parse(fs.readFileSync(packagePath)); + packageJson = $tw.utils.parseJSONSafe(fs.readFileSync(packagePath)); delete packageJson.includeWikis; fs.writeFileSync(packagePath,JSON.stringify(packageJson,null,$tw.config.preferences.jsonSpaces)); return null; diff --git a/core/modules/commands/savelibrarytiddlers.js b/core/modules/commands/savelibrarytiddlers.js index a15bd807c..07521f3ba 100644 --- a/core/modules/commands/savelibrarytiddlers.js +++ b/core/modules/commands/savelibrarytiddlers.js @@ -69,7 +69,7 @@ Command.prototype.execute = function() { $tw.utils.createFileDirectories(pathname); fs.writeFileSync(pathname,JSON.stringify(tiddler),"utf8"); // Collect the skinny list data - var pluginTiddlers = JSON.parse(tiddler.text), + var pluginTiddlers = $tw.utils.parseJSONSafe(tiddler.text), readmeContent = (pluginTiddlers.tiddlers[title + "/readme"] || {}).text, doesRequireReload = !!self.commander.wiki.doesPluginInfoRequireReload(pluginTiddlers), iconTiddler = pluginTiddlers.tiddlers[title + "/icon"] || {}, diff --git a/core/modules/commands/savewikifolder.js b/core/modules/commands/savewikifolder.js index 406f3a3b2..48e9be56a 100644 --- a/core/modules/commands/savewikifolder.js +++ b/core/modules/commands/savewikifolder.js @@ -151,7 +151,7 @@ WikiFolderMaker.prototype.saveCustomPlugin = function(pluginTiddler) { pluginInfo = pluginTiddler.getFieldStrings({exclude: ["text","type"]}); this.saveJSONFile(directory + path.sep + "plugin.info",pluginInfo); self.log("Writing " + directory + path.sep + "plugin.info: " + JSON.stringify(pluginInfo,null,$tw.config.preferences.jsonSpaces)); - var pluginTiddlers = JSON.parse(pluginTiddler.fields.text).tiddlers; // A hashmap of tiddlers in the plugin + var pluginTiddlers = $tw.utils.parseJSONSafe(pluginTiddler.fields.text).tiddlers; // A hashmap of tiddlers in the plugin $tw.utils.each(pluginTiddlers,function(tiddler) { self.saveTiddler(directory,new $tw.Tiddler(tiddler)); }); diff --git a/core/modules/deserializers.js b/core/modules/deserializers.js index bd0a969e2..bff4aaea1 100644 --- a/core/modules/deserializers.js +++ b/core/modules/deserializers.js @@ -17,16 +17,13 @@ exports["application/x-tiddler-html-div"] = function(text,fields) { }; exports["application/json"] = function(text,fields) { - var incoming, - results = []; - try { - incoming = JSON.parse(text); - } catch(e) { - incoming = [{ - title: "JSON error: " + e, - text: "" - }] - } + var results = [], + incoming = $tw.utils.parseJSONSafe(text,function(err) { + return [{ + title: "JSON error: " + err, + text: "" + }]; + }); if(!$tw.utils.isArray(incoming)) { incoming = [incoming]; } diff --git a/core/modules/pluginswitcher.js b/core/modules/pluginswitcher.js index 395dc8b20..2915de95a 100644 --- a/core/modules/pluginswitcher.js +++ b/core/modules/pluginswitcher.js @@ -52,7 +52,7 @@ PluginSwitcher.prototype.switchPlugins = function() { var tiddler = self.wiki.getTiddler(title); if(tiddler && tiddler.isPlugin() && plugins.indexOf(title) === -1) { plugins.push(title); - var pluginInfo = JSON.parse(self.wiki.getTiddlerText(title)), + var pluginInfo = $tw.utils.parseJSONSafe(self.wiki.getTiddlerText(title)), dependents = $tw.utils.parseStringArray(tiddler.fields.dependents || ""); $tw.utils.each(dependents,function(title) { accumulatePlugin(title); diff --git a/core/modules/savers/gitea.js b/core/modules/savers/gitea.js index 1a42ab436..826719ee9 100644 --- a/core/modules/savers/gitea.js +++ b/core/modules/savers/gitea.js @@ -61,7 +61,7 @@ GiteaSaver.prototype.save = function(text,method,callback) { } var use_put = true; if(xhr.status !== 404) { - getResponseData = JSON.parse(getResponseDataJson); + getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson); $tw.utils.each(getResponseData,function(details) { if(details.name === filename) { sha = details.sha; @@ -104,7 +104,7 @@ GiteaSaver.prototype.upload = function(uri,method,headers,data,callback) { if(err) { return callback(err); } - var putResponseData = JSON.parse(putResponseDataJson); + var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson); callback(null); } }); diff --git a/core/modules/savers/github.js b/core/modules/savers/github.js index f8f266d04..f9b87263d 100644 --- a/core/modules/savers/github.js +++ b/core/modules/savers/github.js @@ -61,7 +61,7 @@ GitHubSaver.prototype.save = function(text,method,callback) { return callback(err); } if(xhr.status !== 404) { - getResponseData = JSON.parse(getResponseDataJson); + getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson); $tw.utils.each(getResponseData,function(details) { if(details.name === filename) { sha = details.sha; @@ -84,7 +84,7 @@ GitHubSaver.prototype.save = function(text,method,callback) { if(err) { return callback(err); } - var putResponseData = JSON.parse(putResponseDataJson); + var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson); callback(null); } }); diff --git a/core/modules/savers/gitlab.js b/core/modules/savers/gitlab.js index dad3da618..243aab8a5 100644 --- a/core/modules/savers/gitlab.js +++ b/core/modules/savers/gitlab.js @@ -58,7 +58,7 @@ GitLabSaver.prototype.save = function(text,method,callback) { } var requestType = "POST"; if(xhr.status !== 404) { - getResponseData = JSON.parse(getResponseDataJson); + getResponseData = $tw.utils.parseJSONSafe(getResponseDataJson); $tw.utils.each(getResponseData,function(details) { if(details.name === filename) { requestType = "PUT"; @@ -82,7 +82,7 @@ GitLabSaver.prototype.save = function(text,method,callback) { if(err) { return callback(err); } - var putResponseData = JSON.parse(putResponseDataJson); + var putResponseData = $tw.utils.parseJSONSafe(putResponseDataJson); callback(null); } }); diff --git a/core/modules/server/routes/put-tiddler.js b/core/modules/server/routes/put-tiddler.js index 2966d6e00..ed3b48fcc 100644 --- a/core/modules/server/routes/put-tiddler.js +++ b/core/modules/server/routes/put-tiddler.js @@ -18,7 +18,7 @@ exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/; exports.handler = function(request,response,state) { var title = $tw.utils.decodeURIComponentSafe(state.params[0]), - fields = JSON.parse(state.data); + fields = $tw.utils.parseJSONSafe(state.data); // Pull up any subfields in the `fields` object if(fields.fields) { $tw.utils.each(fields.fields,function(field,name) { diff --git a/core/modules/startup/browser-messaging.js b/core/modules/startup/browser-messaging.js index bee6d4988..25aae9a92 100644 --- a/core/modules/startup/browser-messaging.js +++ b/core/modules/startup/browser-messaging.js @@ -152,7 +152,7 @@ exports.startup = function() { if(event.data.status.charAt(0) === "2") { if(event.data.cookies) { if(event.data.cookies.type === "save-info") { - var tiddlers = JSON.parse(event.data.body); + var tiddlers = $tw.utils.parseJSONSafe(event.data.body); $tw.utils.each(tiddlers,function(tiddler) { $tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),tiddler,{ title: event.data.cookies.infoTitlePrefix + event.data.cookies.url + "/" + tiddler.title, @@ -170,7 +170,7 @@ exports.startup = function() { },$tw.wiki.getModificationFields())); }); } else if(event.data.cookies.type === "save-tiddler") { - var tiddler = JSON.parse(event.data.body); + var tiddler = $tw.utils.parseJSONSafe(event.data.body); $tw.wiki.addTiddler(new $tw.Tiddler(tiddler)); } } diff --git a/core/modules/upgraders/plugins.js b/core/modules/upgraders/plugins.js index a7dd02982..83487aabf 100644 --- a/core/modules/upgraders/plugins.js +++ b/core/modules/upgraders/plugins.js @@ -41,7 +41,7 @@ exports.upgrade = function(wiki,titles,tiddlers) { // Check if we're dealing with a plugin if(incomingTiddler && incomingTiddler["plugin-type"]) { // Check whether the plugin contains JS modules - var requiresReload = wiki.doesPluginInfoRequireReload(JSON.parse(incomingTiddler.text)) ? (wiki.getTiddlerText("$:/language/ControlPanel/Plugins/PluginWillRequireReload") + " ") : ""; + var requiresReload = wiki.doesPluginInfoRequireReload($tw.utils.parseJSONSafe(incomingTiddler.text)) ? (wiki.getTiddlerText("$:/language/ControlPanel/Plugins/PluginWillRequireReload") + " ") : ""; messages[title] = requiresReload; if(incomingTiddler.version) { // Upgrade the incoming plugin if it is in the upgrade library diff --git a/core/modules/utils/crypto.js b/core/modules/utils/crypto.js index 9fffd1d86..8ad0923a4 100644 --- a/core/modules/utils/crypto.js +++ b/core/modules/utils/crypto.js @@ -33,7 +33,7 @@ Attempt to extract the tiddlers from an encrypted store area using the current p exports.decryptStoreArea = function(encryptedStoreArea,password) { var decryptedText = $tw.crypto.decrypt(encryptedStoreArea,password); if(decryptedText) { - var json = JSON.parse(decryptedText), + var json = $tw.utils.parseJSONSafe(decryptedText), tiddlers = []; for(var title in json) { if(title !== "$:/isEncrypted") { diff --git a/core/modules/utils/dom/dragndrop.js b/core/modules/utils/dom/dragndrop.js index 21ecb334a..9536fb9c8 100644 --- a/core/modules/utils/dom/dragndrop.js +++ b/core/modules/utils/dom/dragndrop.js @@ -198,7 +198,7 @@ var importDataTypes = [ ]; function parseJSONTiddlers(json,fallbackTitle) { - var data = JSON.parse(json); + var data = $tw.utils.parseJSONSafe(json); if(!$tw.utils.isArray(data)) { data = [data]; } diff --git a/core/modules/utils/edition-info.js b/core/modules/utils/edition-info.js index d3e7054b0..f8a5cab06 100644 --- a/core/modules/utils/edition-info.js +++ b/core/modules/utils/edition-info.js @@ -30,11 +30,7 @@ exports.getEditionInfo = function() { var entry = entries[entryIndex]; // Check if directories have a valid tiddlywiki.info if(!editionInfo[entry] && $tw.utils.isDirectory(path.resolve(editionPath,entry))) { - var info; - try { - info = JSON.parse(fs.readFileSync(path.resolve(editionPath,entry,"tiddlywiki.info"),"utf8")); - } catch(ex) { - } + var info = $tw.utils.parseJSONSafe(fs.readFileSync(path.resolve(editionPath,entry,"tiddlywiki.info"),"utf8"),null); if(info) { editionInfo[entry] = info; } diff --git a/core/modules/utils/pluginmaker.js b/core/modules/utils/pluginmaker.js index 1fc1a3986..e7426d62b 100644 --- a/core/modules/utils/pluginmaker.js +++ b/core/modules/utils/pluginmaker.js @@ -24,10 +24,8 @@ exports.repackPlugin = function(title,additionalTiddlers,excludeTiddlers) { throw "No such tiddler as " + title; } // Extract the JSON - var jsonPluginTiddler; - try { - jsonPluginTiddler = JSON.parse(pluginTiddler.fields.text); - } catch(e) { + var jsonPluginTiddler = $tw.utils.parseJSONSafe(pluginTiddler.fields.text,null); + if(!jsonPluginTiddler) { throw "Cannot parse plugin tiddler " + title + "\n" + $tw.language.getString("Error/Caption") + ": " + e; } // Get the list of tiddlers diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 4007e64d9..3f1efbe7a 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -498,11 +498,7 @@ NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) { // Import JSON tiddlers into a pending import tiddler NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) { // Get the tiddlers - var tiddlers = []; - try { - tiddlers = JSON.parse(event.param); - } catch(e) { - } + var tiddlers = $tw.utils.parseJSONSafe(event.param,[]); // Get the current $:/Import tiddler var importTitle = event.importTitle ? event.importTitle : IMPORT_TITLE, importTiddler = this.wiki.getTiddler(importTitle), diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 23d0c0d26..87d78344e 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -833,12 +833,7 @@ exports.getTiddlerData = function(titleOrTiddler,defaultData) { switch(tiddler.fields.type) { case "application/json": // JSON tiddler - try { - data = JSON.parse(tiddler.fields.text); - } catch(ex) { - return defaultData; - } - return data; + return $tw.utils.parseJSONSafe(tiddler.fields.text,defaultData); case "application/x-tiddler-dictionary": return $tw.utils.parseFields(tiddler.fields.text); }