/* ======================================================================== * 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.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; 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.NoPlantumlCompressionException; import net.sourceforge.plantuml.png.MetadataTag; import net.sourceforge.plantuml.servlet.utility.Configuration; import net.sourceforge.plantuml.servlet.utility.UmlExtractor; import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; /** * Original idea from Achim Abeling for Confluence macro. * * This class is the old all-in-one historic implementation of the PlantUml server. * See package.html for the new design. It's a work in progress. * * Modified by Arnaud Roques * Modified by Pablo Lalloni * Modified by Maxime Sinclair */ @SuppressWarnings("SERIAL") public class PlantUmlServlet extends AsciiCoderServlet { /** * Default encoded uml text. * Bob -> Alice : hello */ private static final String DEFAULT_ENCODED_TEXT = "SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"; @Override protected String getServletContextPath() { return "uml"; } static { OptionFlags.ALLOW_INCLUDE = false; if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { OptionFlags.ALLOW_INCLUDE = true; } } /** * Encode arbitrary string to HTML string. * * @param string arbitrary string * * @return html encoded string */ public static String stringToHTMLString(String string) { final StringBuilder sb = new StringBuilder(string.length()); // true if last char was blank final int length = string.length(); int offset = 0; while (offset < length) { final int c = string.codePointAt(offset); if (c == ' ') { sb.append(' '); } else if (c == '"') { sb.append("""); } else if (c == '&') { sb.append("&"); } else if (c == '<') { sb.append("<"); } else if (c == '>') { sb.append(">"); } else if (c == '\r') { sb.append("\r"); } else if (c == '\n') { sb.append("\n"); } else { int ci = 0xffffff & c; if (ci < 160) { // nothing special only 7 Bit sb.append((char) c); } else { // Not 7 Bit use the unicode system sb.append("&#"); sb.append(ci); sb.append(';'); } } offset += Character.charCount(c); } return sb.toString(); } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); // textual diagram source final String text = getText(request).trim(); // no Text form has been submitted if (text.isEmpty()) { redirectNow(request, response, DEFAULT_ENCODED_TEXT); return; } // diagram index to render final int idx = UrlDataExtractor.getIndex(request.getRequestURI()); // forward to index.jsp final String path; final String view = request.getParameter("view"); if (view != null && view.equalsIgnoreCase("previewer")) { path = "/previewer.jsp"; } else { path = "/index.jsp"; } prepareRequestForDispatch(request, text, idx); final RequestDispatcher dispatcher = request.getRequestDispatcher(path); dispatcher.forward(request, response); } @Override protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // diagram index to render final int idx = UrlDataExtractor.getIndex(request.getRequestURI()); // encoded diagram source String encoded; try { String text = getText(request).trim(); encoded = getTranscoder().encode(text); } catch (Exception e) { encoded = DEFAULT_ENCODED_TEXT; e.printStackTrace(); } redirectNow(request, response, encoded, idx); } /** * Get textual diagram. * Search for textual diagram in following order: * 1. URL {@link PlantUmlServlet.getTextFromUrl} * 2. metadata * 3. request parameter "text" * * @param request http request * * @return if successful textual diagram source; otherwise empty string * * @throws IOException if an input or output exception occurred */ private String getText(final HttpServletRequest request) throws IOException { String text; // 1. URL try { text = getTextFromUrl(request); if (text != null && !text.isEmpty()) { return text; } } catch (NoPlantumlCompressionException e) { // no textual diagram source available from Url // ignore and try 2. method (metadata) below // do not spam output console } catch (Exception e) { e.printStackTrace(); } // 2. metadata String metadata = request.getParameter("metadata"); if (metadata != null) { try (InputStream img = getImage(new URL(metadata))) { MetadataTag metadataTag = new MetadataTag(img, "plantuml"); String data = metadataTag.getData(); if (data != null) { return data; } } } // 3. request parameter text text = request.getParameter("text"); if (text != null && !text.isEmpty()) { return text; } // nothing found return ""; } /** * Get textual diagram source from URL. * * @param request http request which contains the source URL * * @return if successful textual diagram source from URL; otherwise empty string * * @throws IOException if an input or output exception occurred */ private String getTextFromUrl(HttpServletRequest request) throws IOException { return getTranscoder().decode(getEncodedTextFromUrl(request)); } /** * Prepare request for dispatch and get request dispatcher. * * @param request http request which will be further prepared for dispatch * @param text textual diagram source * * @throws IOException if an input or output exception occurred */ private void prepareRequestForDispatch(HttpServletRequest request, String text, int idx) throws IOException { final String encoded = getTranscoder().encode(text); // diagram sources request.setAttribute("encoded", encoded); request.setAttribute("decoded", text); request.setAttribute("index", (idx < 0) ? "" : idx); // properties request.setAttribute("showSocialButtons", Configuration.get("SHOW_SOCIAL_BUTTONS")); request.setAttribute("showGithubRibbon", Configuration.get("SHOW_GITHUB_RIBBON")); // map for diagram source if necessary String map = ""; if (PlantumlUtils.hasCMapData(text)) { try { map = UmlExtractor.extractMap(text); } catch (Exception e) { e.printStackTrace(); } } request.setAttribute("map", map); } /** * Send redirect response to encoded uml text. * * @param request http request * @param response http response * @param encoded encoded uml text * * @throws IOException if an input or output exception occurred */ private void redirectNow( HttpServletRequest request, HttpServletResponse response, String encoded ) throws IOException { redirectNow(request, response, encoded, null); } /** * Send redirect response to encoded uml text. * * @param request http request * @param response http response * @param encoded encoded uml text * @param index diagram index * * @throws IOException if an input or output exception occurred */ private void redirectNow( HttpServletRequest request, HttpServletResponse response, String encoded, Integer index ) throws IOException { final String path; if (index == null || index < 0) { path = request.getContextPath() + "/uml/" + encoded; } else { path = request.getContextPath() + "/uml/" + index + "/" + encoded; } response.sendRedirect(path); } /** * Get open http connection from URL. * * @param url URL to open connection * * @return open http connection * * @throws IOException if an input or output exception occurred */ private static HttpURLConnection getConnection(URL url) throws IOException { if (url.getProtocol().startsWith("https")) { HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setReadTimeout(10000); // 10 seconds con.connect(); return con; } else { HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setReadTimeout(10000); // 10 seconds con.connect(); return con; } } /** * Get image input stream from URL. * * @param url URL to open connection * * @return response input stream from URL * * @throws IOException if an input or output exception occurred */ private static InputStream getImage(URL url) throws IOException { InputStream is = null; HttpURLConnection con = getConnection(url); is = con.getInputStream(); return is; } }