plantuml-server/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java

347 lines
12 KiB
Java

/* ========================================================================
* 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("&quot;");
} else if (c == '&') {
sb.append("&amp;");
} else if (c == '<') {
sb.append("&lt;");
} else if (c == '>') {
sb.append("&gt;");
} 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;
}
}