From 5a46ee32d08a18843896ac3649832eca3de3e14a Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Sat, 4 Apr 2026 23:33:05 -0500 Subject: [PATCH 1/3] Embed extension jnlps in main to avoid RTT Signed-off-by: Mitch Gaffigan --- .../server/servlets/WebStartServlet.java | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java index ecc077b7a..998bd70d6 100644 --- a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java +++ b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java @@ -264,9 +264,7 @@ protected Document getAdministratorJnlp(HttpServletRequest request) throws Excep } for (String extensionPath : extensionPathsToAddToJnlp) { - Element extensionElement = document.createElement("extension"); - extensionElement.setAttribute("href", "webstart/extensions/" + extensionPath + ".jnlp"); - resourcesElement.appendChild(extensionElement); + getExtensionJnlp(extensionPath, document, resourcesElement); } Element applicationDescElement = (Element) jnlpElement.getElementsByTagName("application-desc").item(0); @@ -320,25 +318,14 @@ protected Document getExtensionJnlp(String extensionPath) throws Exception { List allExtensions = new ArrayList(); allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getConnectorMetaData().values()); allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getPluginMetaData().values()); - Set librariesToAddToJnlp = new HashSet(); List extensionsWithThePath = new ArrayList(); for (MetaData metaData : allExtensions) { if (metaData.getPath().equals(extensionPath)) { extensionsWithThePath.add(metaData.getName()); - - for (ExtensionLibrary library : metaData.getLibraries()) { - if (library.getType().equals(ExtensionLibrary.Type.CLIENT) || library.getType().equals(ExtensionLibrary.Type.SHARED)) { - librariesToAddToJnlp.add(library.getPath()); - } - } } } - if (extensionsWithThePath.isEmpty()) { - throw new Exception("Extension metadata could not be located for the path: " + extensionPath); - } - DocumentBuilderFactory dbf = getSecureDocumentBuilderFactory(); Document document = dbf.newDocumentBuilder().newDocument(); Element jnlpElement = document.createElement("jnlp"); @@ -361,21 +348,50 @@ protected Document getExtensionJnlp(String extensionPath) throws Exception { Element resourcesElement = document.createElement("resources"); + getExtensionJnlp(extensionPath, document, resourcesElement, "libs/"); + + jnlpElement.appendChild(resourcesElement); + jnlpElement.appendChild(document.createElement("component-desc")); + document.appendChild(jnlpElement); + return document; + } + + protected void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement) throws Exception { + getExtensionJnlp(extensionPath, document, resourcesElement, "webstart/extensions/libs/"); + } + + private void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement, String libraryPathPrefix) throws Exception { + List allExtensions = new ArrayList(); + allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getConnectorMetaData().values()); + allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getPluginMetaData().values()); + Set librariesToAddToJnlp = new HashSet(); + List extensionsWithThePath = new ArrayList(); + + for (MetaData metaData : allExtensions) { + if (metaData.getPath().equals(extensionPath)) { + extensionsWithThePath.add(metaData.getName()); + + for (ExtensionLibrary library : metaData.getLibraries()) { + if (library.getType().equals(ExtensionLibrary.Type.CLIENT) || library.getType().equals(ExtensionLibrary.Type.SHARED)) { + librariesToAddToJnlp.add(library.getPath()); + } + } + } + } + + if (extensionsWithThePath.isEmpty()) { + throw new Exception("Extension metadata could not be located for the path: " + extensionPath); + } + File extensionDirectory = new File(ExtensionController.getExtensionsPath() + extensionPath); for (String library : librariesToAddToJnlp) { Element jarElement = document.createElement("jar"); jarElement.setAttribute("download", "eager"); - // this path is relative to the servlet path - jarElement.setAttribute("href", "libs/" + extensionPath + "/" + library); + jarElement.setAttribute("href", libraryPathPrefix + extensionPath + "/" + library); jarElement.setAttribute("sha256", getDigest(extensionDirectory, library)); resourcesElement.appendChild(jarElement); } - - jnlpElement.appendChild(resourcesElement); - jnlpElement.appendChild(document.createElement("component-desc")); - document.appendChild(jnlpElement); - return document; } private String getDigest(File directory, String filePath) throws Exception { From 9b67d61967f320519a7cafb631e508838011433c Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Sat, 4 Apr 2026 23:44:52 -0500 Subject: [PATCH 2/3] Remove dead extension jnlp server endpoint Signed-off-by: Mitch Gaffigan --- .../mirth/connect/server/MirthWebServer.java | 1 - .../server/servlets/WebStartServlet.java | 65 +---------- .../server/servlets/WebStartServletTest.java | 103 +----------------- 3 files changed, 4 insertions(+), 165 deletions(-) diff --git a/server/src/com/mirth/connect/server/MirthWebServer.java b/server/src/com/mirth/connect/server/MirthWebServer.java index d18f2f1ea..4c9820dcb 100644 --- a/server/src/com/mirth/connect/server/MirthWebServer.java +++ b/server/src/com/mirth/connect/server/MirthWebServer.java @@ -347,7 +347,6 @@ public boolean accept(File file) { servletContextHandler.addFilter(new FilterHolder(new MethodFilter()), "/*", EnumSet.of(DispatcherType.REQUEST)); servletContextHandler.addServlet(new ServletHolder(new WebStartServlet()), "/webstart.jnlp"); servletContextHandler.addServlet(new ServletHolder(new WebStartServlet()), "/webstart"); - servletContextHandler.addServlet(new ServletHolder(new WebStartServlet()), "/webstart/extensions/*"); handlers.addHandler(servletContextHandler); // add the default handler for misc requests (favicon, etc.) diff --git a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java index 998bd70d6..fd43c63f3 100644 --- a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java +++ b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java @@ -30,7 +30,6 @@ import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilderFactory; -import com.mirth.connect.client.core.BrandingConstants; import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.commons.lang3.StringUtils; @@ -85,10 +84,6 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro if ((request.getRequestURI().equals(contextPathProp + "/webstart.jnlp") || request.getRequestURI().equals(contextPathProp + "/webstart")) && isWebstartRequestValid(request)) { jnlpDocument = getAdministratorJnlp(request); response.setHeader("Content-Disposition", "attachment; filename = \"webstart.jnlp\""); - } else if (request.getServletPath().equals("/webstart/extensions") && isWebstartExtensionsRequestValid(request, contextPathProp)) { - String extensionPath = getExtensionPath(request); - jnlpDocument = getExtensionJnlp(getExtensionPath(request)); - response.setHeader("Content-Disposition", "attachment; filename = \"" + extensionPath + ".jnlp\""); } else { response.setContentType(""); } @@ -123,16 +118,6 @@ private boolean isWebstartRequestValid(HttpServletRequest request) { return true; } - private boolean isWebstartExtensionsRequestValid(HttpServletRequest request, String contextPathProp) { - // Don't allow any parameters and don't allow modified URIs - return request.getParameterMap().isEmpty() - && (contextPathProp + request.getServletPath() + "/" + getExtensionPath(request)).equals(StringUtils.removeEnd(request.getRequestURI(), ".jnlp")); - } - - private String getExtensionPath(HttpServletRequest request) { - return StringUtils.removeEnd(StringUtils.removeStart(request.getPathInfo(), "/"), ".jnlp"); - } - protected Document getAdministratorJnlp(HttpServletRequest request) throws Exception { InputStream clientJnlpIs = null; Document document; @@ -314,53 +299,7 @@ private boolean doesExtensionHaveClientOrSharedLibraries(MetaData extension) { return false; } - protected Document getExtensionJnlp(String extensionPath) throws Exception { - List allExtensions = new ArrayList(); - allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getConnectorMetaData().values()); - allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getPluginMetaData().values()); - List extensionsWithThePath = new ArrayList(); - - for (MetaData metaData : allExtensions) { - if (metaData.getPath().equals(extensionPath)) { - extensionsWithThePath.add(metaData.getName()); - } - } - - DocumentBuilderFactory dbf = getSecureDocumentBuilderFactory(); - Document document = dbf.newDocumentBuilder().newDocument(); - Element jnlpElement = document.createElement("jnlp"); - - Element informationElement = document.createElement("information"); - - Element titleElement = document.createElement("title"); - titleElement.setTextContent("Mirth Connect Extension - [" + StringUtils.join(extensionsWithThePath, ",") + "]"); - informationElement.appendChild(titleElement); - - Element vendorElement = document.createElement("vendor"); - vendorElement.setTextContent(BrandingConstants.COMPANY_NAME); - informationElement.appendChild(vendorElement); - - jnlpElement.appendChild(informationElement); - - Element securityElement = document.createElement("security"); - securityElement.appendChild(document.createElement("all-permissions")); - jnlpElement.appendChild(securityElement); - - Element resourcesElement = document.createElement("resources"); - - getExtensionJnlp(extensionPath, document, resourcesElement, "libs/"); - - jnlpElement.appendChild(resourcesElement); - jnlpElement.appendChild(document.createElement("component-desc")); - document.appendChild(jnlpElement); - return document; - } - - protected void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement) throws Exception { - getExtensionJnlp(extensionPath, document, resourcesElement, "webstart/extensions/libs/"); - } - - private void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement, String libraryPathPrefix) throws Exception { + private void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement) throws Exception { List allExtensions = new ArrayList(); allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getConnectorMetaData().values()); allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getPluginMetaData().values()); @@ -388,7 +327,7 @@ private void getExtensionJnlp(String extensionPath, Document document, Element r for (String library : librariesToAddToJnlp) { Element jarElement = document.createElement("jar"); jarElement.setAttribute("download", "eager"); - jarElement.setAttribute("href", libraryPathPrefix + extensionPath + "/" + library); + jarElement.setAttribute("href", "webstart/extensions/libs/" + extensionPath + "/" + library); jarElement.setAttribute("sha256", getDigest(extensionDirectory, library)); resourcesElement.appendChild(jarElement); } diff --git a/server/test/com/mirth/connect/server/servlets/WebStartServletTest.java b/server/test/com/mirth/connect/server/servlets/WebStartServletTest.java index be3c9b31e..66d770dc9 100644 --- a/server/test/com/mirth/connect/server/servlets/WebStartServletTest.java +++ b/server/test/com/mirth/connect/server/servlets/WebStartServletTest.java @@ -203,91 +203,6 @@ public void testDoGetCoreModifiedURL() throws Exception { assertNull(response.getHeader("Content-Disposition")); } - @Test - public void testDoGetExtension() throws Exception { - // Test /webstart/extensions/testextension - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/webstart/extensions/testextension"); - when(request.getServletPath()).thenReturn("/webstart/extensions"); - when(request.getPathInfo()).thenReturn("/testextension"); - when(request.getParameterNames()).thenReturn(Collections.emptyEnumeration()); - - TestHttpServletResponse response = new TestHttpServletResponse(); - - webStartServlet.doGet(request, response); - - assertEquals(normalizeWhitespace(EXTENSION_JNLP), normalizeWhitespace(response.getResponseString())); - assertEquals("application/x-java-jnlp-file", response.getContentType()); - assertEquals("no-cache", response.getHeader("Pragma")); - assertEquals("nosniff", response.getHeader("X-Content-Type-Options")); - assertEquals("attachment; filename = \"testextension.jnlp\"", response.getHeader("Content-Disposition")); - - // Test /webstart/extensions/testextension.jnlp - request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/webstart/extensions/testextension.jnlp"); - when(request.getServletPath()).thenReturn("/webstart/extensions"); - when(request.getPathInfo()).thenReturn("/testextension"); - when(request.getParameterNames()).thenReturn(Collections.emptyEnumeration()); - - response = new TestHttpServletResponse(); - - webStartServlet.doGet(request, response); - - assertEquals(normalizeWhitespace(EXTENSION_JNLP), normalizeWhitespace(response.getResponseString())); - assertEquals("application/x-java-jnlp-file", response.getContentType()); - assertEquals("no-cache", response.getHeader("Pragma")); - assertEquals("nosniff", response.getHeader("X-Content-Type-Options")); - assertEquals("attachment; filename = \"testextension.jnlp\"", response.getHeader("Content-Disposition")); - } - - @Test - public void testDoGetExtensionQueryParams() throws Exception { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/webstart/extensions/testextension"); - when(request.getServletPath()).thenReturn("/webstart/extensions"); - when(request.getPathInfo()).thenReturn("/testextension"); - - Map parameters = new HashMap<>(); - parameters.put("maxHeapSize", new String[] { "1024m" }); - - when(request.getParameterNames()).thenReturn(Collections.enumeration(parameters.keySet())); - when(request.getParameter(anyString())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - return parameters.get((String) args[0])[0]; - } - }); - when(request.getParameterMap()).thenReturn(parameters); - - TestHttpServletResponse response = new TestHttpServletResponse(); - - webStartServlet.doGet(request, response); - - assertEquals("", response.getResponseString().trim()); - assertEquals("", response.getResponseString().trim()); - assertEquals("", response.getContentType()); - assertNull(response.getHeader("Content-Disposition")); - } - - @Test - public void testDoGetExtensionModifiedURL() throws Exception { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/webstart/extensions/testextension;rfd.bat"); - when(request.getServletPath()).thenReturn("/webstart/extensions"); - when(request.getPathInfo()).thenReturn("/testextension"); - when(request.getParameterNames()).thenReturn(Collections.emptyEnumeration()); - - TestHttpServletResponse response = new TestHttpServletResponse(); - - webStartServlet.doGet(request, response); - - assertEquals("", response.getResponseString().trim()); - assertEquals("", response.getResponseString().trim()); - assertEquals("", response.getContentType()); - assertNull(response.getHeader("Content-Disposition")); - } - private static class TestHttpServletResponse implements HttpServletResponse { private String contentType; @@ -521,13 +436,6 @@ protected Document getAdministratorJnlp(HttpServletRequest request) throws Excep return factory.newDocumentBuilder() .parse(new ByteArrayInputStream(CORE_JNLP.getBytes())); } - - @Override - protected Document getExtensionJnlp(String extensionPath) throws Exception { - DocumentBuilderFactory factory = getSecureDocumentBuilderFactory(); - return factory.newDocumentBuilder() - .parse(new ByteArrayInputStream(EXTENSION_JNLP.getBytes())); - } } private static DocumentBuilderFactory getSecureDocumentBuilderFactory() throws Exception { @@ -558,16 +466,9 @@ private static String normalizeWhitespace(String input) { + " \n" + " \n" + " \n" - + " \n" + " \n" + " \n" + + " \n" + + " \n" + " \n" + " \n" + " \n" + " https://localhost:8443\n" + " 4.5.2\n" + " \n" + ""; - - private static String EXTENSION_JNLP = "\n" + " \n" - + " Mirth Connect Extension - [Test Writer,Test Reader]\n" - + " NextGen Healthcare\n" + " \n" + " \n" - + " \n" + " \n" + " \n" - + " \n" - + " \n" - + " \n" + " \n" + "\n" + ""; } From 77ad485e5a974709e9c9f21a026c710b921a8441 Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Sat, 4 Apr 2026 23:49:20 -0500 Subject: [PATCH 3/3] Avoid duplicate work in getExtensionJnlp Signed-off-by: Mitch Gaffigan --- .../connect/server/servlets/WebStartServlet.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java index fd43c63f3..8bb847055 100644 --- a/server/src/com/mirth/connect/server/servlets/WebStartServlet.java +++ b/server/src/com/mirth/connect/server/servlets/WebStartServlet.java @@ -249,7 +249,7 @@ protected Document getAdministratorJnlp(HttpServletRequest request) throws Excep } for (String extensionPath : extensionPathsToAddToJnlp) { - getExtensionJnlp(extensionPath, document, resourcesElement); + getExtensionJnlp(extensionPath, allExtensions, document, resourcesElement); } Element applicationDescElement = (Element) jnlpElement.getElementsByTagName("application-desc").item(0); @@ -299,16 +299,13 @@ private boolean doesExtensionHaveClientOrSharedLibraries(MetaData extension) { return false; } - private void getExtensionJnlp(String extensionPath, Document document, Element resourcesElement) throws Exception { - List allExtensions = new ArrayList(); - allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getConnectorMetaData().values()); - allExtensions.addAll(ControllerFactory.getFactory().createExtensionController().getPluginMetaData().values()); + private void getExtensionJnlp(String extensionPath, List allExtensions, Document document, Element resourcesElement) throws Exception { Set librariesToAddToJnlp = new HashSet(); - List extensionsWithThePath = new ArrayList(); + boolean foundExtensionPath = false; for (MetaData metaData : allExtensions) { if (metaData.getPath().equals(extensionPath)) { - extensionsWithThePath.add(metaData.getName()); + foundExtensionPath = true; for (ExtensionLibrary library : metaData.getLibraries()) { if (library.getType().equals(ExtensionLibrary.Type.CLIENT) || library.getType().equals(ExtensionLibrary.Type.SHARED)) { @@ -318,7 +315,7 @@ private void getExtensionJnlp(String extensionPath, Document document, Element r } } - if (extensionsWithThePath.isEmpty()) { + if (!foundExtensionPath) { throw new Exception("Extension metadata could not be located for the path: " + extensionPath); }