diff --git a/js/ArgParser.js b/js/ArgParser.js index deb1be5dd..0d892f7d0 100755 --- a/js/ArgParser.js +++ b/js/ArgParser.js @@ -14,7 +14,11 @@ Options and their defaults are: defaultName: null, defaultValue: null, noNames: false, - cascadeDefaults: false + cascadeDefaults: false, + allowEval: true + globals: null + +`globals` is the global variable object provided to evaluated parameters \*/ (function(){ @@ -22,23 +26,26 @@ Options and their defaults are: /*jslint node: true */ "use strict"; +var Sandbox = require("./Sandbox.js").Sandbox; + var ArgParser = function(argString,options) { var parseToken = function(match,p) { - var n; - if(match[p]) // Double quoted - n = match[p]; - else if(match[p+1]) // Single quoted - n = match[p+1]; - else if(match[p+2]) // Double-square-bracket quoted - n = match[p+2]; - else if(match[p+3]) // Double-brace quoted - n = match[p+3]; - else if(match[p+4]) // Unquoted - n = match[p+4]; - else if(match[p+5]) // empty quote - n = ""; - return n; - }; + var n; + if(match[p]) { // Double quoted + n = match[p]; + } else if(match[p+1]) { // Single quoted + n = match[p+1]; + } else if(match[p+2]) { // Double-square-bracket quoted + n = match[p+2]; + } else if(match[p+3]) { // Double-brace quoted + n = options.allowEval === false ? match[p+3] : Sandbox(match[p+3],options.globals); + } else if(match[p+4]) { // Unquoted + n = match[p+4]; + } else if(match[p+5]) { // empty quote + n = ""; + } + return n; + }; this.byPos = []; var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")", sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')", diff --git a/js/Sandbox.js b/js/Sandbox.js new file mode 100644 index 000000000..3cc61eccc --- /dev/null +++ b/js/Sandbox.js @@ -0,0 +1,53 @@ +/*\ +title: js/Sandbox.js + +Execute a fragment of JavaScript in a sandbox + +\*/ +(function(){ + +/*jslint node: true */ +"use strict"; + +var uglify = require("uglify-js"); + +var safeEval = function(e) { + return eval(e); +}; + +var Sandbox = function(code,globals) { + var globalNames = [], + globalValues = [], + collectGlobals = function(globals) { + if(globals) { + for(var g in globals) { + globalNames.push(g); + globalValues.push(globals[g]); + } + } + }; + // Collect the supplied globals + collectGlobals(globals); + // Add the default globals + collectGlobals({ + tiddlywiki: "5" + }); + // Compose the code + var out = []; + out.push("(function(") + out.push(globalNames.join(",")); + out.push(") { return "); + out.push(code); + out.push(";})"); + // Parse the code + var tree = uglify.parser.parse(out.join("")); + // XXX: Sanitise the code by checking for references to globals + // Recompile the code + var compiledCode = uglify.uglify.gen_code(tree); + // Execute it + return eval(compiledCode).apply(null,globalValues); +}; + +exports.Sandbox = Sandbox; + +})(); \ No newline at end of file diff --git a/js/WikiTextRenderer.js b/js/WikiTextRenderer.js index 61dd7f051..8f67e5232 100644 --- a/js/WikiTextRenderer.js +++ b/js/WikiTextRenderer.js @@ -129,7 +129,19 @@ WikiTextRenderer.prototype.executeMacro = function(macroNode,title) { var macroInfo = WikiTextRenderer.macros[macroNode.name]; macroNode.output = []; if(macroInfo) { - macroInfo.handler.call(this,macroNode,title); + var args; + if(macroInfo.argOptions) { + var argOptions = { + globals: { + title: title + } + }; + for(var g in macroInfo.argOptions) { + argOptions[g] = macroInfo.argOptions[g]; + } + args = new ArgParser(macroNode.params,argOptions); + } + macroInfo.handler.call(this,macroNode,args,title); } else { macroNode.output.push({type: "text", value: "Unknown macro " + macroNode.name}); } @@ -139,18 +151,25 @@ WikiTextRenderer.versionTiddlyWiki = "2.6.5"; WikiTextRenderer.macros = { allTags: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, br: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { macroNode.output.push({type: "br"}); } }, + echo: { + argOptions: {defaultName: "anon"}, + handler: function(macroNode,args,title) { + var globals = {title: title}; + macroNode.output.push({type: "text", value: args.byPos[0].v}); + } + }, timeline: { - handler: function(macroNode,title) { - var args = new ArgParser(macroNode.params,{defaultName:"anon"}), - anonByPos = args.getValuesByName("anon",[]), + argOptions: {defaultName:"anon"}, + handler: function(macroNode,args,title) { + var anonByPos = args.getValuesByName("anon",[]), field = anonByPos[0] || "modified", limit = anonByPos[1] || null, dateformat = anonByPos[2] || "DD MMM YYYY", @@ -214,9 +233,9 @@ WikiTextRenderer.macros = { } }, list: { - handler: function(macroNode,title) { - var args = new ArgParser(macroNode.params,{defaultName:"type"}), - type = args.getValueByName("type","all"), + argOptions: {defaultName:"type"}, + handler: function(macroNode,args,title) { + var type = args.getValueByName("type","all"), template = args.getValueByName("template",null), templateType = "text/x-tiddlywiki", templateText = "<>", emptyMessage = args.getValueByName("emptyMessage",null); @@ -275,29 +294,29 @@ WikiTextRenderer.macros = { } }, slider: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, tabs: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, tag: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, tagging: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, tags: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { } }, tiddler: { - handler: function(macroNode,title) { - var args = new ArgParser(macroNode.params,{defaultName:"name",cascadeDefaults:true}), - targetTitle = args.getValueByName("name",null), + argOptions: {defaultName:"name",cascadeDefaults:true}, + handler: function(macroNode,args,title) { + var targetTitle = args.getValueByName("name",null), withTokens = args.getValuesByName("with",[]), tiddler = this.store.getTiddler(targetTitle), text = this.store.getTiddlerText(targetTitle,""), @@ -312,22 +331,22 @@ WikiTextRenderer.macros = { } }, today: { - handler: function(macroNode,title) { + argOptions: {noNames:true}, + handler: function(macroNode,args,title) { var now = new Date(), - args = new ArgParser(macroNode.params,{noNames:true}), value = args.byPos[0] ? utils.formatDateString(now,args.byPos[0].v) : now.toLocaleString(); macroNode.output.push({type: "text", value: value}); } }, version: { - handler: function(macroNode,title) { + handler: function(macroNode,args,title) { macroNode.output.push({type: "text", value: WikiTextRenderer.versionTiddlyWiki}); } }, view: { - handler: function(macroNode,title) { - var args = new ArgParser(macroNode.params,{noNames:true}), - field = args.byPos[0] ? args.byPos[0].v : null, + argOptions: {noNames:true}, + handler: function(macroNode,args,title) { + var field = args.byPos[0] ? args.byPos[0].v : null, format = args.byPos[1] ? args.byPos[1].v : "text", tiddler = this.store.getTiddler(title), value = tiddler ? tiddler.fields[field] : null; diff --git a/tiddlywiki5/BootLoader.js b/tiddlywiki5/BootLoader.js index 2db599857..15ceede96 100644 --- a/tiddlywiki5/BootLoader.js +++ b/tiddlywiki5/BootLoader.js @@ -7,7 +7,6 @@ Given the absolute path of a srcModule, and a relative reference to a dstModule, return the fully resolved module name */ function resolveModuleName(srcModule,dstModule) { -console.log("Resolving " + dstModule + " in the context of " + srcModule); var src = srcModule.split("/"), dst = dstModule.split("/"), c; diff --git a/tiddlywiki5/tiddlers/HelloThere.tid b/tiddlywiki5/tiddlers/HelloThere.tid index 8b1ec1b1f..c2260fc1e 100644 --- a/tiddlywiki5/tiddlers/HelloThere.tid +++ b/tiddlywiki5/tiddlers/HelloThere.tid @@ -8,3 +8,5 @@ TiddlyWiki is a unique [[wiki|WikiWikiWeb]] that people [[love using|Raves]] to TiddlyWiki is written in [[HTML]], [[CSS]] and JavaScript to run on any reasonably modern [[browser|Browsers]] without needing any ServerSide logic. It allows anyone to create personal SelfContained hypertext documents that can be published to a WebServer, sent by email, stored in a DropBox or kept on a USB thumb drive to make a WikiOnAStick. Because it doesn't need to be installed and configured it makes a great GuerillaWiki. This is revision <> of TiddlyWiki, and is published under an OpenSourceLicense. Unlike most wikis, TiddlyWiki doesn't directly support group collaboration; it is a wiki in the sense of elevating linking be a part of the punctuation of writing. You can easily publish a TiddlyWiki you have created by placing the single file on a web server (for instance the homepage hosting provided by many ISPs). If you need full group collaboration features, there are several HostedOptions to choose from. + +<> diff --git a/tiddlywiki5/tiddlywiki5.recipe b/tiddlywiki5/tiddlywiki5.recipe index 2e179fec8..db48a47ff 100644 --- a/tiddlywiki5/tiddlywiki5.recipe +++ b/tiddlywiki5/tiddlywiki5.recipe @@ -9,6 +9,7 @@ recipe: tiddlers/split.recipe jslib: ../test/tiddlywiki.2.6.5/source/tiddlywiki/jquery/jquery.js +jsmodule: ../js/Sandbox.js jsmodule: ../js/ArgParser.js jsmodule: ../js/FileRetriever.js jsmodule: ../js/Utils.js