David Benson vor 3 Jahren
Ursprung
Commit
064729fec4

+ 8 - 0
ChangeLog

@@ -1,3 +1,11 @@
+26-MAY-2022: 18.1.3
+
+- Adds spacing dialog for parallels layout
+- Adds allowlist for layout constructor names
+- Adds JSON values for childLayout styles
+- Adds size check for fetched connection data
+- Allows custom protocols in links
+
 23-MAY-2022: 18.1.2
 
 - Limits export proxy URL

+ 1 - 1
VERSION

@@ -1 +1 @@
-18.1.2
+18.1.3

+ 10 - 1
src/main/java/com/mxgraph/online/ConverterServlet.java

@@ -35,6 +35,7 @@ public class ConverterServlet  extends HttpServlet
 			.getLogger(HttpServlet.class.getName());
 	
 	private static final int MAX_DIM = 5000;
+	private static final int MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
 	private static final double EMF_10thMM2PXL = 26.458;
 	private static final String API_KEY_FILE_PATH = "/WEB-INF/cloud_convert_api_key"; // Not migrated to new pattern, since will not be used on diagrams.net
 	private static final String CONVERT_SERVICE_URL = "https://api.cloudconvert.com/convert";
@@ -177,11 +178,19 @@ public class ConverterServlet  extends HttpServlet
 				}
 
 				addParameterHeader("file", fileName, postRequest);
-				
+				int total = 0;
+
 				while(bytesRead != -1) 
 				{
 					postRequest.write(data, 0, bytesRead);
 					bytesRead = fileContent.read(data);
+					total += bytesRead;
+
+					if (total > MAX_FILE_SIZE)
+					{
+						postRequest.close();
+						throw new Exception("File size exceeds the maximum allowed size of " + MAX_FILE_SIZE + " bytes.");
+					}
 				}
 				
 				postRequest.writeBytes(CRLF + TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + CRLF);

+ 15 - 1
src/main/java/com/mxgraph/online/EmbedServlet2.java

@@ -69,6 +69,11 @@ public class EmbedServlet2 extends HttpServlet
 	 */
 	protected static String lastModified = null;
 
+	/**
+	 * Max fetch size
+	 */
+	protected static int MAX_FETCH_SIZE = 50 * 1024 * 1024; // 50 MB
+
 	/**
 	 * 
 	 */
@@ -392,6 +397,7 @@ public class EmbedServlet2 extends HttpServlet
 		if (urls != null)
 		{
 			HashSet<String> completed = new HashSet<String>();
+			int sizeLimit = MAX_FETCH_SIZE;
 
 			for (int i = 0; i < urls.length; i++)
 			{
@@ -405,7 +411,15 @@ public class EmbedServlet2 extends HttpServlet
 						URLConnection connection = url.openConnection();
 						((HttpURLConnection) connection).setInstanceFollowRedirects(false);
 						ByteArrayOutputStream stream = new ByteArrayOutputStream();
-						Utils.copy(connection.getInputStream(), stream);
+						String contentLength = connection.getHeaderField("Content-Length");
+
+						// If content length is available, use it to enforce maximum size
+						if (contentLength != null && Long.parseLong(contentLength) > sizeLimit)
+						{
+							break;
+						}
+
+						sizeLimit -= Utils.copyRestricted(connection.getInputStream(), stream, sizeLimit);
 						setCachedUrls += "GraphViewer.cachedUrls['"
 								+ StringEscapeUtils.escapeEcmaScript(urls[i])
 								+ "'] = decodeURIComponent('"

+ 3 - 2
src/main/java/com/mxgraph/online/ExportProxyServlet.java

@@ -21,7 +21,8 @@ import javax.servlet.http.HttpServletResponse;
 public class ExportProxyServlet extends HttpServlet
 {
 	private final String[] supportedServices = {"EXPORT_URL", "PLANTUML_URL", "VSD_CONVERT_URL", "EMF_CONVERT_URL"};
-	
+	private static int MAX_FETCH_SIZE = 50 * 1024 * 1024; // 50 MB
+
 	private void doRequest(String method, HttpServletRequest request,
 			HttpServletResponse response) throws ServletException, IOException
 	{
@@ -102,7 +103,7 @@ public class ExportProxyServlet extends HttpServlet
 				con.setDoOutput(true);
 				
 				OutputStream params = con.getOutputStream();
-				Utils.copy(request.getInputStream(), params);
+				Utils.copyRestricted(request.getInputStream(), params, MAX_FETCH_SIZE);
 				params.flush();
 				params.close();
 	        }

+ 24 - 2
src/main/java/com/mxgraph/online/ProxyServlet.java

@@ -45,6 +45,11 @@ public class ProxyServlet extends HttpServlet
 	 */
 	private static final int TIMEOUT = 29000;
 	
+	/**
+	 * Max fetch size
+	 */
+	protected static int MAX_FETCH_SIZE = 50 * 1024 * 1024; // 50 MB
+
 	/**
 	 * A resuable empty byte array instance.
 	 */
@@ -136,6 +141,14 @@ public class ProxyServlet extends HttpServlet
 					{
 						response.setStatus(status);
 						
+						String contentLength = connection.getHeaderField("Content-Length");
+
+						// If content length is available, use it to enforce maximum size
+						if (contentLength != null && Long.parseLong(contentLength) > MAX_FETCH_SIZE)
+						{
+							throw new UnsupportedContentException();
+						}
+
 						// Copies input stream to output stream
 						InputStream is = connection.getInputStream();
 						byte[] head = (contentAlwaysAllowed(urlParam)) ? emptyBytes
@@ -208,6 +221,8 @@ public class ProxyServlet extends HttpServlet
 	{
 		if (base64)
 		{
+			int total = 0;
+
 			try (BufferedInputStream in = new BufferedInputStream(is,
 					BUFFER_SIZE))
 			{
@@ -217,7 +232,14 @@ public class ProxyServlet extends HttpServlet
 				os.write(head, 0, head.length);
 				
 			    for (int len = is.read(buffer); len != -1; len = is.read(buffer))
-			    { 
+			    {
+					total += len;
+
+					if (total > MAX_FETCH_SIZE)
+					{
+						throw new IOException("Size limit exceeded");
+					}
+
 			        os.write(buffer, 0, len);
 			    }
 
@@ -227,7 +249,7 @@ public class ProxyServlet extends HttpServlet
 		else
 		{
 			out.write(head);
-			Utils.copy(is, out);
+			Utils.copyRestricted(is, out, MAX_FETCH_SIZE);
 		}
 	}
 

+ 36 - 1
src/main/java/com/mxgraph/online/Utils.java

@@ -145,6 +145,18 @@ public class Utils
 		copy(in, out, IO_BUFFER_SIZE);
 	}
 
+	/**
+	 * Copies the input stream to the output stream using the default buffer size
+	 * @param in the input stream
+	 * @param out the output stream
+	 * @param sizeLimit the maximum number of bytes to copy
+	 * @throws IOException
+	 */
+	public static int copyRestricted(InputStream in, OutputStream out, int sizeLimit) throws IOException
+	{
+		return copy(in, out, IO_BUFFER_SIZE, sizeLimit);
+	}
+
 	/**
 	 * Copies the input stream to the output stream using the specified buffer size
 	 * @param in the input stream
@@ -154,14 +166,37 @@ public class Utils
 	 */
 	public static void copy(InputStream in, OutputStream out, int bufferSize)
 			throws IOException
+	{
+		copy(in, out, bufferSize, 0);
+	}
+
+	/**
+	 * Copies the input stream to the output stream using the specified buffer size
+	 * @param in the input stream
+	 * @param out the output stream
+	 * @param bufferSize the buffer size to use when copying
+	 * @param sizeLimit the maximum number of bytes to copy
+	 * @throws IOException
+	 */
+	public static int copy(InputStream in, OutputStream out, int bufferSize, int sizeLimit)
+			throws IOException
 	{
 		byte[] b = new byte[bufferSize];
-		int read;
+		int read, total = 0;
 
 		while ((read = in.read(b)) != -1)
 		{
+			total += read;
+
+			if (sizeLimit > 0 && total > sizeLimit)
+			{
+				throw new IOException("Size limit exceeded");
+			}
+
 			out.write(b, 0, read);
 		}
+
+		return total;
 	}
 
 	/**

Datei-Diff unterdrückt, da er zu groß ist
+ 905 - 901
src/main/webapp/js/app.min.js


+ 4 - 31
src/main/webapp/js/diagramly/EditorUi.js

@@ -12237,7 +12237,7 @@
 					}
 					else if (data.action == 'layout')
 					{
-						this.executeLayoutList(data.layouts)
+						this.executeLayouts(this.editor.graph.createLayouts(data.layouts));
 
 						return;
 					}
@@ -13223,35 +13223,6 @@
 		this.showDialog(this.importCsvDialog.container, 640, 520, true, true, null, null, null, null, true);
 		this.importCsvDialog.init();
 	};
-
-
-	/**
-	 * Runs the layout from the given JavaScript array which is of the form [{layout: name, config: obj}, ...]
-	 * where name is the layout constructor name and config contains the properties of the layout instance.
-	 */
-	EditorUi.prototype.executeLayoutList = function(layoutList, done)
-	{
-		var graph = this.editor.graph;
-		var cells = graph.getSelectionCells();
-
-		for (var i = 0; i < layoutList.length; i++)
-		{
-			var layout = new window[layoutList[i].layout](graph);
-			
-			if (layoutList[i].config != null)
-			{
-				for (var key in layoutList[i].config)
-				{
-					layout[key] = layoutList[i].config[key];
-				}
-			}
-			
-			this.executeLayout(function()
-			{
-				layout.execute(graph.getDefaultParent(), cells.length == 0 ? null : cells);
-			}, i == layoutList.length - 1, done);
-		}
-	};
 	
 	/**
 	 *
@@ -13862,11 +13833,13 @@
 			    			// Required for layouts to work with new cells
 							var temp = afterInsert;
 			    			graph.view.validate();
-							this.executeLayoutList(JSON.parse(layout), function()
+
+							this.executeLayouts(graph.createLayouts(JSON.parse(layout)), function()
 							{
 								postProcess();
 								temp();
 							});
+
 							afterInsert = null;
 						}
 						else if (layout == 'circle')

+ 13 - 0
src/main/webapp/js/diagramly/ElectronApp.js

@@ -668,6 +668,19 @@ mxStencilRegistry.allowEval = false;
 				
 				var extPluginsBtn = mxUtils.button(mxResources.get('selectFile') + '...', async function()
 				{
+					var warningMsgs = mxResources.get('pluginWarning').split('\\n');
+					var warningMsg = warningMsgs.pop(); //Last line in the message
+
+					if (!warningMsg) 
+					{
+						warningMsg = warningMsgs.pop();
+					}
+
+					if (!confirm(warningMsg)) 
+					{
+						return;
+					}
+					
 					var lastDir = localStorage.getItem('.lastPluginDir');
 					
 					var paths = await requestSync({

+ 16 - 17
src/main/webapp/js/diagramly/Menus.js

@@ -1329,18 +1329,14 @@
 				{
 					try
 					{
-						var layoutList = JSON.parse(newValue);
-						editorUi.executeLayoutList(layoutList)
-						editorUi.customLayoutConfig = layoutList;
+						var list = JSON.parse(newValue);
+						editorUi.executeLayouts(graph.createLayouts(list));
+						editorUi.customLayoutConfig = list;
+						editorUi.hideDialog();
 					}
 					catch (e)
 					{
 						editorUi.handleError(e);
-						
-						if (window.console != null)
-						{
-							console.error(e);
-						}
 					}
 				}
 			}, null, null, null, null, null, true, null, null,
@@ -1514,21 +1510,24 @@
 				
 				editorUi.showDialog(dlg.container, 355, 140, true, true);
 			}, parent, null, isGraphEnabled());
-						
+			
 			menu.addSeparator(parent);
-		
+			
 			menu.addItem(mxResources.get('parallels'), null, mxUtils.bind(this, function()
 			{
-				// Keeps parallel edges apart
 				var layout = new mxParallelEdgeLayout(graph);
 				layout.checkOverlap = true;
-				layout.spacing = 20;
+
+				editorUi.prompt(mxResources.get('spacing'), layout.spacing, mxUtils.bind(this, function(newValue)
+				{
+					layout.spacing = newValue;
 				
-	    		editorUi.executeLayout(function()
-	    		{
-	    			layout.execute(graph.getDefaultParent(), (!graph.isSelectionEmpty()) ?
-	    				graph.getSelectionCells() : null);
-	    		}, false);
+					editorUi.executeLayout(function()
+					{
+						layout.execute(graph.getDefaultParent(), (!graph.isSelectionEmpty()) ?
+							graph.getSelectionCells() : null);
+					}, false);
+				}));
 			}), parent);
 			
 			menu.addSeparator(parent);

+ 1 - 1
src/main/webapp/js/diagramly/Minimal.js

@@ -1587,8 +1587,8 @@ EditorUi.initMinimalTheme = function()
 					}
 					
 					ui.menus.addMenuItems(menu, ['insertImage', 'insertLink', '-'], parent);
-					ui.menus.addSubmenu('insertLayout', menu, parent, mxResources.get('layout'));
 					ui.menus.addSubmenu('insertAdvanced', menu, parent, mxResources.get('advanced'));
+					ui.menus.addSubmenu('layout', menu, parent);
 				}
 				else
 				{

+ 33 - 0
src/main/webapp/js/grapheditor/EditorUi.js

@@ -4576,6 +4576,23 @@ EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange)
 	});	
 };
 
+/**
+ * Translates this point by the given vector.
+ * 
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.prompt = function(title, defaultValue, fn)
+{
+	var dlg = new FilenameDialog(this, defaultValue, mxResources.get('apply'), function(newValue)
+	{
+		fn(parseFloat(newValue));
+	}, title);
+
+	this.showDialog(dlg.container, 300, 80, true, true);
+	dlg.init();
+};
+
 /**
  * Translates this point by the given vector.
  * 
@@ -5176,6 +5193,22 @@ EditorUi.prototype.save = function(name)
 	}
 };
 
+/**
+ * Executes the given array of graph layouts using executeLayout and
+ * calls done after the last layout has finished.
+ */
+EditorUi.prototype.executeLayouts = function(layouts, post)
+{
+	this.executeLayout(mxUtils.bind(this, function()
+	{
+		var layout = new mxCompositeLayout(this.editor.graph, layouts);
+		var cells = this.editor.graph.getSelectionCells();
+
+		layout.execute(this.editor.graph.getDefaultParent(),
+			cells.length == 0 ? null : cells);
+	}), true, post);
+};
+
 /**
  * Executes the given layout.
  */

+ 60 - 5
src/main/webapp/js/grapheditor/Graph.js

@@ -1345,6 +1345,14 @@ Graph.pasteStyles = ['rounded', 'shadow', 'dashed', 'dashPattern', 'fontFamily',
 					'arcSize', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 'disableMultiStroke',
 					'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 'simplification', 'comicStyle'];
 
+/**
+ * Whitelist for known layout names.
+ */
+Graph.layoutNames = ['mxHierarchicalLayout', 'mxCircleLayout',
+	'mxCompactTreeLayout', 'mxEdgeLabelLayout', 'mxFastOrganicLayout',
+	'mxParallelEdgeLayout', 'mxPartitionLayout', 'mxRadialTreeLayout',
+	'mxStackLayout'];
+
 /**
  * Creates a temporary graph instance for rendering off-screen content.
  */
@@ -1753,7 +1761,8 @@ Graph.sanitizeNode = function(value)
 // Allows use tag in SVG with local references only
 DOMPurify.addHook('afterSanitizeAttributes', function(node)
 {
-	if (node.hasAttribute('xlink:href') && !node.getAttribute('xlink:href').match(/^#/))
+	if (node.nodeName == 'use' && node.hasAttribute('xlink:href') &&
+		!node.getAttribute('xlink:href').match(/^#/))
 	{
 		node.remove();
 	} 
@@ -1793,10 +1802,7 @@ Graph.clipSvgDataUri = function(dataUri, ignorePreserveAspect)
 			if (idx >= 0)
 			{
 				// Strips leading XML declaration and doctypes
-				div.innerHTML = data.substring(idx);
-
-				// Removes all attributes starting with on
-				Graph.sanitizeNode(div);
+				div.innerHTML = Graph.sanitizeHtml(data.substring(idx));
 				
 				// Gets the size and removes from DOM
 				var svgs = div.getElementsByTagName('svg');
@@ -3201,12 +3207,61 @@ Graph.prototype.initLayoutManager = function()
 			{
 				return new TableLayout(this.graph);
 			}
+			else if (style['childLayout'] != null && style['childLayout'].charAt(0) == '[')
+			{
+				try
+				{
+					return new mxCompositeLayout(this.graph,
+						this.graph.createLayouts(JSON.parse(
+							style['childLayout'])));
+				}
+				catch (e)
+				{
+					if (window.console != null)
+					{
+						console.error(e);
+					}
+				}
+			}
 		}
 		
 		return null;
 	};
 };
 
+/**
+ * Creates an array of graph layouts from the given array of the form [{layout: name, config: obj}, ...]
+ * where name is the layout constructor name and config contains the properties of the layout instance.
+ */
+Graph.prototype.createLayouts = function(list)
+{
+	var layouts = [];
+
+	for (var i = 0; i < list.length; i++)
+	{
+		if (mxUtils.indexOf(Graph.layoutNames, list[i].layout) >= 0)
+		{
+			var layout = new window[list[i].layout](this);
+			
+			if (list[i].config != null)
+			{
+				for (var key in list[i].config)
+				{
+					layout[key] = list[i].config[key];
+				}
+			}
+
+			layouts.push(layout);
+		}
+		else
+		{
+			throw Error(mxResources.get('invalidCallFnNotFound', [list[i].layout]));
+		}
+	}
+
+	return layouts;
+};
+
 /**
  * Returns the metadata of the given cells as a JSON object.
  */

+ 1 - 1
src/main/webapp/js/grapheditor/Init.js

@@ -9,7 +9,7 @@ window.urlParams = window.urlParams || {};
 // Public global variables
 window.DOM_PURIFY_CONFIG = window.DOM_PURIFY_CONFIG ||
     {ADD_TAGS: ['use'], ADD_ATTR: ['target'], FORBID_TAGS: ['form'],
-    ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel|callto|data):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i};
+    ALLOWED_URI_REGEXP: /^((?!javascript:).)*$/i};
 window.MAX_REQUEST_SIZE = window.MAX_REQUEST_SIZE  || 10485760;
 window.MAX_AREA = window.MAX_AREA || 15000 * 15000;
 

+ 1 - 6
src/main/webapp/js/grapheditor/Menus.js

@@ -311,12 +311,7 @@ Menus.prototype.init = function()
 	{
 		var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
 		{
-			var dlg = new FilenameDialog(this.editorUi, defaultValue, mxResources.get('apply'), function(newValue)
-			{
-				fn(parseFloat(newValue));
-			}, mxResources.get('spacing'));
-			this.editorUi.showDialog(dlg.container, 300, 80, true, true);
-			dlg.init();
+			this.editorUi.prompt(mxResources.get('spacing'), defaultValue, fn);
 		});
 
 		var runTreeLayout = mxUtils.bind(this, function(layout)

Datei-Diff unterdrückt, da er zu groß ist
+ 905 - 901
src/main/webapp/js/integrate.min.js


Datei-Diff unterdrückt, da er zu groß ist
+ 661 - 657
src/main/webapp/js/viewer-static.min.js


Datei-Diff unterdrückt, da er zu groß ist
+ 661 - 657
src/main/webapp/js/viewer.min.js


Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 1
src/main/webapp/mxgraph/mxClient.js


Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 1
src/main/webapp/service-worker.js


Datei-Diff unterdrückt, da er zu groß ist
+ 1 - 1
src/main/webapp/service-worker.js.map