Fix security issue by avoiding use of eval() within postMessage calls between embedAPI and main editor (also namespace the messages and protect the imagelib extension message listener from non-string messages); avoid embedAPI's unneeded randomizing of callback IDs in favor of incrementing; deprecate old embedded_svg_edit API name in favor of JS/JSLint-friendly EmbeddedSVGEdit name (and allow it to be instantiated w/o new keyword); JSLint/HTML5-ize embedAPI files, remove HTML5/browser-optional type="text/javascript", remove unused comments for embedAPI

git-svn-id: http://svg-edit.googlecode.com/svn/trunk@2585 eee81c28-f429-11dd-99c0-75d572ba1ddd
master
Brett Zamir 2013-10-13 23:59:32 +00:00
parent 109cbaf99b
commit ffde8814ac
4 changed files with 92 additions and 95 deletions

View File

@ -1,19 +1,23 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" > <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" />
<title>Embed API</title> <title>Embed API</title>
</head> <script src="jquery.js"></script>
<body> <script src="embedapi.js"></script>
<script type="text/javascript" src="embedapi.js"></script> <script>
<script type="text/javascript"> /*globals $, EmbeddedSVGEdit*/
var svgCanvas = null; $(function () {'use strict';
var svgCanvas = null;
function init_embed() { function init_embed() {
var frame = document.getElementById('svgedit'); var doc, mainButton,
svgCanvas = new embedded_svg_edit(frame); frame = document.getElementById('svgedit');
// Hide main button, as we will be controlling new/load/save etc from the host document svgCanvas = new EmbeddedSVGEdit(frame);
var doc = frame.contentDocument || frame.contentWindow.document; // Hide main button, as we will be controlling new, load, save, etc. from the host document
var mainButton = doc.getElementById('main_button'); doc = frame.contentDocument || frame.contentWindow.document;
mainButton = doc.getElementById('main_button');
mainButton.style.display = 'none'; mainButton.style.display = 'none';
} }
@ -33,10 +37,20 @@
function saveSvg() { function saveSvg() {
svgCanvas.getSvgString()(handleSvgData); svgCanvas.getSvgString()(handleSvgData);
} }
// Add event handlers
$('#load').click(loadSvg);
$('#save').click(saveSvg);
// Export globals
window.init_embed = init_embed;
});
</script> </script>
<button onclick="loadSvg();">Load example</button> </head>
<button onclick="saveSvg();">Save data</button> <body>
<button id="load">Load example</button>
<button id="save">Save data</button>
<br/> <br/>
<iframe src="svg-editor.html" width="900px" height="600px" id="svgedit" onload="init_embed()"></iframe> <iframe src="svg-editor.html" width="900px" height="600px" id="svgedit" onload="init_embed();"></iframe>
</body> </body>
</html> </html>

View File

@ -1,42 +1,10 @@
/* /*
function embedded_svg_edit(frame){
//initialize communication
this.frame = frame;
this.stack = []; //callback stack
var editapi = this;
window.addEventListener("message", function(e){
if(e.data.substr(0,5) == "ERROR"){
editapi.stack.splice(0,1)[0](e.data,"error")
}else{
editapi.stack.splice(0,1)[0](e.data)
}
}, false)
}
embedded_svg_edit.prototype.call = function(code, callback){
this.stack.push(callback);
this.frame.contentWindow.postMessage(code,"*");
}
embedded_svg_edit.prototype.getSvgString = function(callback){
this.call("svgCanvas.getSvgString()", callback)
}
embedded_svg_edit.prototype.setSvgString = function(svg){
this.call("svgCanvas.setSvgString('"+svg.replace(/'/g, "\\'")+"')");
}
*/
/*
Embedded SVG-edit API Embedded SVG-edit API
General usage: General usage:
- Have an iframe somewhere pointing to a version of svg-edit > r1000 - Have an iframe somewhere pointing to a version of svg-edit > r1000
- Initialize the magic with: - Initialize the magic with:
var svgCanvas = new embedded_svg_edit(window.frames['svgedit']); var svgCanvas = new EmbeddedSVGEdit(window.frames['svgedit']);
- Pass functions in this format: - Pass functions in this format:
svgCanvas.setSvgString("string") svgCanvas.setSvgString("string")
- Or if a callback is needed: - Or if a callback is needed:
@ -52,15 +20,33 @@ Everything is done with the same API as the real svg-edit,
and all documentation is unchanged. The only difference is and all documentation is unchanged. The only difference is
when handling returns, the callback notation is used instead. when handling returns, the callback notation is used instead.
var blah = new embedded_svg_edit(window.frames['svgedit']); var blah = new EmbeddedSVGEdit(window.frames['svgedit']);
blah.clearSelection("woot","blah",1337,[1,2,3,4,5,"moo"],-42,{a: "tree",b:6, c: 9})(function(){console.log("GET DATA",arguments)}) blah.clearSelection("woot","blah",1337,[1,2,3,4,5,"moo"],-42,{a: "tree",b:6, c: 9})(function(){console.log("GET DATA",arguments)})
*/ */
function embedded_svg_edit(frame){ (function () {'use strict';
//initialize communication
var cbid = 0;
function getCallbackSetter (d) {
return function(){
var t = this, // new callback
args = [].slice.call(arguments),
cbid = t.send(d, args, function(){}); // the callback (currently it's nothing, but will be set later)
return function(newcallback){
t.callbacks[cbid] = newcallback; // set callback
};
};
}
function EmbeddedSVGEdit(frame){
if (!(this instanceof EmbeddedSVGEdit)) { // Allow invocation without "new" keyword
return new EmbeddedSVGEdit(frame);
}
// initialize communication
this.frame = frame; this.frame = frame;
//this.stack = [] //callback stack this.callbacks = {}; // successor to stack
this.callbacks = {}; //successor to stack
//List of functions extracted with this: //List of functions extracted with this:
//Run in firebug on http://svg-edit.googlecode.com/svn/trunk/docs/files/svgcanvas-js.html //Run in firebug on http://svg-edit.googlecode.com/svn/trunk/docs/files/svgcanvas-js.html
@ -72,7 +58,9 @@ function embedded_svg_edit(frame){
//Newer, well, it extracts things that aren't documented as well. All functions accessible through the normal thingy can now be accessed though the API //Newer, well, it extracts things that aren't documented as well. All functions accessible through the normal thingy can now be accessed though the API
//var l=[];for(var i in svgCanvas){if(typeof svgCanvas[i] == "function"){l.push(i)}}; //var l=[];for(var i in svgCanvas){if(typeof svgCanvas[i] == "function"){l.push(i)}};
//run in svgedit itself //run in svgedit itself
var functions = ["updateElementFromJson", "embedImage", "fixOperaXML", "clearSelection", "addToSelection", var i,
t = this,
functions = ["updateElementFromJson", "embedImage", "fixOperaXML", "clearSelection", "addToSelection",
"removeFromSelection", "addNodeToSelection", "open", "save", "getSvgString", "setSvgString", "createLayer", "removeFromSelection", "addNodeToSelection", "open", "save", "getSvgString", "setSvgString", "createLayer",
"deleteCurrentLayer", "getCurrentDrawing", "setCurrentLayer", "renameCurrentLayer", "setCurrentLayerPosition", "deleteCurrentLayer", "getCurrentDrawing", "setCurrentLayer", "renameCurrentLayer", "setCurrentLayerPosition",
"setLayerVisibility", "moveSelectedToLayer", "clear", "clearPath", "getNodePoint", "clonePathNode", "deletePathNode", "setLayerVisibility", "moveSelectedToLayer", "clear", "clearPath", "getNodePoint", "clonePathNode", "deletePathNode",
@ -88,51 +76,41 @@ function embedded_svg_edit(frame){
"getNextRedoCommandText", "undo", "redo", "cloneSelectedElements", "alignSelectedElements", "getZoom", "getVersion", "getNextRedoCommandText", "undo", "redo", "cloneSelectedElements", "alignSelectedElements", "getZoom", "getVersion",
"setIconSize", "setLang", "setCustomHandlers"]; "setIconSize", "setLang", "setCustomHandlers"];
//TODO: rewrite the following, it's pretty scary. // TODO: rewrite the following, it's pretty scary.
for(var i = 0; i < functions.length; i++){ for(i = 0; i < functions.length; i++){
this[functions[i]] = (function(d){ this[functions[i]] = getCallbackSetter(functions[i]);
return function(){
var t = this; //new callback
for(var g = 0, args = []; g < arguments.length; g++){
args.push(arguments[g]);
}
var cbid = t.send(d, args, function(){}); //the callback (currently it's nothing, but will be set later
return function(newcallback){
t.callbacks[cbid] = newcallback; //set callback
};
};
})(functions[i]);
} }
//TODO: use AddEvent for Trident browsers, currently they dont support SVG, but they do support onmessage
var t = this; // Older IE may need a polyfill for addEventListener, but so it would for SVG
window.addEventListener("message", function(e){ window.addEventListener("message", function(e){
if(e.data.substr(0,4) == "SVGe"){ //because svg-edit is too longish if (!e.data || typeof e.data !== "object" || e.data.namespace !== "svg-edit") {
var data = e.data.substr(4); return;
var cbid = data.substr(0, data.indexOf(";")); }
if(t.callbacks[cbid]){ var data = e.data.result || e.data.error,
if(data.substr(cbid.length + 1,6) != "error:"){ cbid = e.data.id;
t.callbacks[cbid](eval("("+data.substr(cbid.length+1)+")")); if(t.callbacks[cbid]){
}else{ if(e.data.result){
t.callbacks[cbid](data, "error"); t.callbacks[cbid](data);
} }else{
t.callbacks[cbid](data, "error");
} }
} }
//this.stack.shift()[0](e.data,e.data.substr(0,5) == "ERROR"?'error':null) //replace with shift
}, false); }, false);
} }
embedded_svg_edit.prototype.send = function(name, args, callback){ EmbeddedSVGEdit.prototype.send = function(name, args, callback){
var cbid = Math.floor(Math.random()*31776352877+993577).toString();
//this.stack.push(callback);
this.callbacks[cbid] = callback;
for(var argstr = [], i = 0; i < args.length; i++){
argstr.push(JSON.stringify(args[i]));
}
var t = this; var t = this;
setTimeout(function(){//delay for the callback to be set in case its synchronous cbid++;
t.frame.contentWindow.postMessage(cbid+";svgCanvas['"+name+"']("+argstr.join(",")+")","*");
this.callbacks[cbid] = callback;
setTimeout(function(){ // delay for the callback to be set in case its synchronous
// Todo: Handle non-JSON arguments and return values (undefined, nonfinite numbers, functions, and built-in objects like Date, RegExp), etc.?
t.frame.contentWindow.postMessage({namespace: "svgCanvas", id: cbid, name: name, args: args}, '*');
}, 0); }, 0);
return cbid; return cbid;
//this.stack.shift()("svgCanvas['"+name+"']("+argstr.join(",")+")")
}; };
window.embedded_svg_edit = EmbeddedSVGEdit; // Export old, deprecated API
window.EmbeddedSVGEdit = EmbeddedSVGEdit; // Follows common JS convention of CamelCase and, as enforced in JSLint, of initial caps for constructors
}());

View File

@ -64,7 +64,7 @@ svgEditor.addExtension("imagelib", function() {
// Receive postMessage data // Receive postMessage data
var response = evt.data; var response = evt.data;
if(!response) { if(!response || typeof response !== "string") { // Todo: Should namespace postMessage API for this extension and filter out here
// Do nothing // Do nothing
return; return;
} }

View File

@ -4571,11 +4571,16 @@
// Callback handler for embedapi.js // Callback handler for embedapi.js
try { try {
window.addEventListener('message', function(e) { window.addEventListener('message', function(e) {
var cbid = parseInt(e.data.substr(0, e.data.indexOf(';')), 10); if (!e.data || typeof e.data !== 'object' || e.data.namespace !== 'svgCanvas') {
return;
}
var cbid = e.data.id,
name = e.data.name,
args = e.data.args;
try { try {
e.source.postMessage('SVGe'+cbid+';'+JSON.stringify(eval(e.data)), '*'); e.source.postMessage({namespace: 'svg-edit', id: cbid, result: svgCanvas[name](args)}, '*');
} catch(err) { } catch(err) {
e.source.postMessage('SVGe'+cbid+';error:'+err.message, '*'); e.source.postMessage({namespace: 'svg-edit', id: cbid, error: err.message}, '*');
} }
}, false); }, false);
} catch(err) { } catch(err) {