diff --git a/src/main/java/net/sourceforge/plantuml/servlet/AsciiCoderServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/AsciiCoderServlet.java new file mode 100644 index 0000000..bcff40a --- /dev/null +++ b/src/main/java/net/sourceforge/plantuml/servlet/AsciiCoderServlet.java @@ -0,0 +1,151 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * Project Info: https://plantuml.com + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +package net.sourceforge.plantuml.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.sourceforge.plantuml.code.Transcoder; +import net.sourceforge.plantuml.code.TranscoderUtil; +import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; + +/** + * ASCII encoder and decoder servlet for the webapp. + * This servlet encodes the diagram in text format or decodes the compressed diagram string. + */ +@SuppressWarnings("SERIAL") +public class AsciiCoderServlet extends HttpServlet { + + /** + * Regex pattern to fetch last part of the URL. + */ + private static final Pattern URL_PATTERN = Pattern.compile("^.*[^a-zA-Z0-9\\-\\_]([a-zA-Z0-9\\-\\_]+)"); + + /** + * Context path from the servlet mapping URL pattern. + * + * @return servlet context path without leading or tailing slash + */ + protected String getServletContextPath() { + return "coder"; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + request.setCharacterEncoding("UTF-8"); + + final String encodedText = getEncodedTextFromUrl(request); + + String text = ""; + try { + text = getTranscoder().decode(encodedText); + } catch (Exception e) { + response.setStatus(500); + e.printStackTrace(); + } + + response.addHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write(text); + } + + @Override + protected void doPost( + HttpServletRequest request, + HttpServletResponse response + ) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + + // read textual diagram source from request body + final StringBuilder uml = new StringBuilder(); + try (BufferedReader in = request.getReader()) { + String line; + while ((line = in.readLine()) != null) { + uml.append(line).append('\n'); + } + } + + // encode textual diagram source + String encoded = ""; + try { + encoded = getTranscoder().encode(uml.toString()); + } catch (Exception e) { + response.setStatus(500); + e.printStackTrace(); + } + + response.addHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write(encoded); + } + + /** + * Get PlantUML transcoder. + * + * @return transcoder instance + */ + protected Transcoder getTranscoder() { + return TranscoderUtil.getDefaultTranscoder(); + } + + /** + * Get encoded textual diagram source from URL. + * + * @param request http request which contains the source URL + * + * @return if successful encoded textual diagram source from URL; otherwise empty string + * + * @throws IOException if an input or output exception occurred + */ + protected String getEncodedTextFromUrl(HttpServletRequest request) throws IOException { + // textual diagram source from request URI + String url = request.getRequestURI(); + final String contextpath = "/" + getServletContextPath() + "/"; + if (url.contains(contextpath) && !url.endsWith(contextpath)) { + final String encoded = UrlDataExtractor.getEncodedDiagram(request.getRequestURI(), ""); + if (!encoded.isEmpty()) { + return encoded; + } + } + // textual diagram source from "url" parameter + url = request.getParameter("url"); + if (url != null && !url.trim().isEmpty()) { + // Catch the last part of the URL if necessary + final Matcher matcher = URL_PATTERN.matcher(url); + if (matcher.find()) { + url = matcher.group(1); + } + return url; + } + // nothing found + return ""; + } + +} diff --git a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java index 1444a0b..123067e 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java @@ -27,21 +27,15 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import javax.net.ssl.HttpsURLConnection; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.api.PlantumlUtils; -import net.sourceforge.plantuml.code.Transcoder; -import net.sourceforge.plantuml.code.TranscoderUtil; import net.sourceforge.plantuml.png.MetadataTag; import net.sourceforge.plantuml.servlet.utility.Configuration; import net.sourceforge.plantuml.servlet.utility.UmlExtractor; @@ -58,7 +52,7 @@ import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; * Modified by Maxime Sinclair */ @SuppressWarnings("SERIAL") -public class PlantUmlServlet extends HttpServlet { +public class PlantUmlServlet extends AsciiCoderServlet { /** * Default encoded uml text. @@ -66,10 +60,10 @@ public class PlantUmlServlet extends HttpServlet { */ private static final String DEFAULT_ENCODED_TEXT = "SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"; - /** - * Regex pattern to fetch last part of the URL. - */ - private static final Pattern URL_PATTERN = Pattern.compile("^.*[^a-zA-Z0-9\\-\\_]([a-zA-Z0-9\\-\\_]+)"); + @Override + protected String getServletContextPath() { + return "uml"; + } static { OptionFlags.ALLOW_INCLUDE = false; @@ -78,11 +72,19 @@ public class PlantUmlServlet extends HttpServlet { } } + /** + * Encode arbitrary string to HTML string. + * + * @param string arbitrary string + * + * @return html encoded string + */ public static String stringToHTMLString(String string) { - final StringBuffer sb = new StringBuffer(string.length()); + final StringBuilder sb = new StringBuilder(string.length()); // true if last char was blank final int length = string.length(); - for (int offset = 0; offset < length;) { + int offset = 0; + while (offset < length) { final int c = string.codePointAt(offset); if (c == ' ') { sb.append(' '); @@ -115,7 +117,6 @@ public class PlantUmlServlet extends HttpServlet { return sb.toString(); } - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); @@ -215,26 +216,7 @@ public class PlantUmlServlet extends HttpServlet { * @throws IOException if an input or output exception occurred */ private String getTextFromUrl(HttpServletRequest request) throws IOException { - // textual diagram source from request URI - String url = request.getRequestURI(); - if (url.contains("/uml/") && !url.endsWith("/uml/")) { - final String encoded = UrlDataExtractor.getEncodedDiagram(request.getRequestURI(), ""); - if (!encoded.isEmpty()) { - return getTranscoder().decode(encoded); - } - } - // textual diagram source from "url" parameter - url = request.getParameter("url"); - if (url != null && !url.trim().isEmpty()) { - // Catch the last part of the URL if necessary - final Matcher matcher = URL_PATTERN.matcher(url); - if (matcher.find()) { - url = matcher.group(1); - } - return getTranscoder().decode(url); - } - // nothing found - return ""; + return getTranscoder().decode(getEncodedTextFromUrl(request)); } /** @@ -318,15 +300,6 @@ public class PlantUmlServlet extends HttpServlet { response.sendRedirect(path); } - /** - * Get PlantUML transcoder. - * - * @return transcoder instance - */ - private Transcoder getTranscoder() { - return TranscoderUtil.getDefaultTranscoder(); - } - /** * Get open http connection from URL. * @@ -341,7 +314,6 @@ public class PlantUmlServlet extends HttpServlet { HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setReadTimeout(10000); // 10 seconds - // printHttpsCert(con); con.connect(); return con; } else { diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index bcdef3d..9d5ea77 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -201,6 +201,15 @@ /language + + asciicoder + net.sourceforge.plantuml.servlet.AsciiCoderServlet + + + asciicoder + /coder/* + + diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestAsciiCoder.java b/src/test/java/net/sourceforge/plantuml/servlet/TestAsciiCoder.java new file mode 100644 index 0000000..8a6c6c7 --- /dev/null +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestAsciiCoder.java @@ -0,0 +1,61 @@ +package net.sourceforge.plantuml.servlet; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + + +public class TestAsciiCoder extends WebappTestCase { + + /** + * Verifies the decoding for the Bob -> Alice sample + */ + public void testBobAliceSampleDiagramDecoding() throws IOException { + final URL url = new URL(getServerUrl() + "/coder/" + TestUtils.SEQBOB); + final URLConnection conn = url.openConnection(); + // Analyze response + // Verifies the Content-Type header + assertEquals( + "Response content type is not TEXT PLAIN or UTF-8", + "text/plain;charset=utf-8", + conn.getContentType().toLowerCase() + ); + // Get and verify the content + final String diagram = getContentText(conn); + assertEquals(TestUtils.SEQBOBCODE, diagram); + } + + /** + * Verifies the encoding for the Bob -> Alice sample + */ + public void testBobAliceSampleDiagramEncoding() throws IOException { + final URL url = new URL(getServerUrl() + "/coder"); + final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-type", "text/plain"); + try (final OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream())) { + writer.write(TestUtils.SEQBOBCODE); + writer.flush(); + } + // Analyze response + // HTTP response 200 + assertEquals( + "Bad HTTP status received", + 200, + conn.getResponseCode() + ); + // Verifies the Content-Type header + assertEquals( + "Response content type is not TEXT PLAIN or UTF-8", + "text/plain;charset=utf-8", + conn.getContentType().toLowerCase() + ); + // Get the content and verify its size + final String diagram = getContentText(conn.getInputStream()); + assertEquals(TestUtils.SEQBOB, diagram); + } + +} diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestForm.java b/src/test/java/net/sourceforge/plantuml/servlet/TestForm.java index 894ecd1..ba54846 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/TestForm.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestForm.java @@ -28,7 +28,7 @@ public class TestForm extends WebappTestCase { assertEquals(2, forms.size()); // Ensure the Text field is correct String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent(); - assertEquals("@startuml\nBob -> Alice : hello\n@enduml", text); + assertEquals(TestUtils.SEQBOBCODE, text); // Ensure the URL field is correct HtmlInput url = forms.get(1).getInputByName("url"); assertNotNull(url); @@ -57,7 +57,7 @@ public class TestForm extends WebappTestCase { assertEquals(2, forms.size()); // Ensure the Text field is correct String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent(); - assertEquals("@startuml\nversion\n@enduml", text); + assertEquals(TestUtils.VERSIONCODE, text); // Ensure the URL field is correct HtmlInput url = forms.get(1).getInputByName("url"); assertNotNull(url); @@ -86,7 +86,7 @@ public class TestForm extends WebappTestCase { assertEquals(2, forms.size()); // Ensure the Text field is correct String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent(); - assertEquals("@startuml\nBob -> Alice : hello\n@enduml", text); + assertEquals(TestUtils.SEQBOBCODE, text); // Ensure the URL field is correct HtmlInput url = forms.get(1).getInputByName("url"); assertNotNull(url); @@ -116,7 +116,7 @@ public class TestForm extends WebappTestCase { assertEquals(2, forms.size()); // Ensure the Text field is correct String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent(); - assertEquals("@startuml\nBob -> Alice : hello\n@enduml", text); + assertEquals(TestUtils.SEQBOBCODE, text); // Ensure the URL field is correct url = forms.get(1).getInputByName("url"); assertNotNull(url); @@ -204,7 +204,7 @@ public class TestForm extends WebappTestCase { assertEquals(2, forms.size()); // Ensure the Text field is correct String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent(); - assertEquals("@startuml\nBob -> Alice : hello\n@enduml", text); + assertEquals(TestUtils.SEQBOBCODE, text); // Ensure the URL field is correct HtmlInput url = forms.get(1).getInputByName("url"); assertNotNull(url); diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestUtils.java b/src/test/java/net/sourceforge/plantuml/servlet/TestUtils.java index b66e85e..6257793 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/TestUtils.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestUtils.java @@ -15,9 +15,19 @@ public abstract class TestUtils { */ public static final String VERSION = "AqijAixCpmC0"; + /** + * version + */ + public static final String VERSIONCODE = "@startuml\nversion\n@enduml"; + /** * Bob -> Alice : hello */ public static final String SEQBOB = "SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"; + /** + * Bob -> Alice : hello + */ + public static final String SEQBOBCODE = "@startuml\nBob -> Alice : hello\n@enduml"; + }