From ec7b9f9b1a9f6ceae6c53023ab08b6a22f564169 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 11 May 2023 20:58:36 +0200 Subject: [PATCH] Add `metadata` Servlet - add new servlet to get meta data from PlantUML diagram - meta data get not only be requested as text but also as json if you set the `Accept`-header to json - add `metadata` servlet tests - GET: like the Proxy where you can pass a URL which the servlet will use to fetch the diagram image - POST: file upload --- pom.jdk8.xml | 6 + pom.xml | 6 + src/main/config/checkstyle.xml | 4 + .../plantuml/servlet/MetadataServlet.java | 376 ++++++++++++++++++ .../plantuml/servlet/ProxyServlet.java | 48 ++- src/main/webapp/WEB-INF/web.xml | 8 + src/main/webapp/resource/test/bob.png | Bin 0 -> 2561 bytes src/main/webapp/resource/test/bob.svg | 2 + .../resource/{ => test}/test2diagrams.txt | 0 .../plantuml/servlet/TestMetadata.java | 146 +++++++ .../plantuml/servlet/TestOldProxy.java | 6 +- .../plantuml/servlet/TestProxy.java | 10 +- .../servlet/utils/WebappTestCase.java | 4 +- 13 files changed, 594 insertions(+), 22 deletions(-) create mode 100644 src/main/java/net/sourceforge/plantuml/servlet/MetadataServlet.java create mode 100644 src/main/webapp/resource/test/bob.png create mode 100644 src/main/webapp/resource/test/bob.svg rename src/main/webapp/resource/{ => test}/test2diagrams.txt (100%) create mode 100644 src/test/java/net/sourceforge/plantuml/servlet/TestMetadata.java diff --git a/pom.jdk8.xml b/pom.jdk8.xml index fe43e7c..1e7f40b 100644 --- a/pom.jdk8.xml +++ b/pom.jdk8.xml @@ -202,6 +202,12 @@ ${junit.version} test + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + org.junit.platform junit-platform-suite-api diff --git a/pom.xml b/pom.xml index ef62d0e..bf07c23 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,12 @@ ${junit.version} test + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + org.junit.platform junit-platform-suite-api diff --git a/src/main/config/checkstyle.xml b/src/main/config/checkstyle.xml index 313d601..7806ff6 100644 --- a/src/main/config/checkstyle.xml +++ b/src/main/config/checkstyle.xml @@ -15,6 +15,8 @@ + + @@ -25,6 +27,7 @@ + @@ -113,5 +116,6 @@ + diff --git a/src/main/java/net/sourceforge/plantuml/servlet/MetadataServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/MetadataServlet.java new file mode 100644 index 0000000..d80aad9 --- /dev/null +++ b/src/main/java/net/sourceforge/plantuml/servlet/MetadataServlet.java @@ -0,0 +1,376 @@ +/* ======================================================================== + * 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.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.MultipartConfig; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Part; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.code.NoPlantumlCompressionException; +import net.sourceforge.plantuml.code.TranscoderUtil; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.klimt.drawing.svg.SvgGraphics; +import net.sourceforge.plantuml.png.MetadataTag; + +/** + * Meta data servlet for the webapp. + * This servlet responses with the meta data of a specific file as text report or JSON object. + */ +@SuppressWarnings("SERIAL") +@MultipartConfig +public class MetadataServlet extends HttpServlet { + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + request.setCharacterEncoding("UTF-8"); + final String urlString = request.getParameter("src"); + // validate URL + final URL url = ProxyServlet.validateURL(urlString, response); + if (url == null) { + return; // error is already set/handled inside `validateURL` + } + // fetch image via URL and extract meta data from it + final HttpURLConnection conn = ProxyServlet.getConnection(url); + try (InputStream is = conn.getInputStream()) { + handleRequest(request, response, is, conn.getContentType(), null); + } + } + + @Override + protected void doPost( + HttpServletRequest request, + HttpServletResponse response + ) throws IOException, ServletException { + request.setCharacterEncoding("UTF-8"); + // get image via file upload + final Part filePart = request.getPart("diagram"); + final String filename = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MS IE fix + try (InputStream is = filePart.getInputStream()) { + handleRequest(request, response, is, null, filename); + } + } + + /** + * Handle request no matter whether GET or POST and + * response with the PlantUML diagram image in the in the desired format if possible. + * + * @param request an HttpServletRequest object that contains the request the client has made of the servlet + * @param response an HttpServletResponse object that contains the response the servlet sends to the client + * @param is PlantUML diagram image as input stream + * @param contentType the PlantUML diagram image content type [optional] + * @param filename the PlantUML diagram image filename [optional + * + * @throws IOException if an input or output error is detected when the servlet handles the request + */ + private void handleRequest( + HttpServletRequest request, + HttpServletResponse response, + InputStream is, + String contentType, + String filename + ) throws IOException { + final String formString = request.getParameter("format"); + final String accept = request.getHeader("Accept"); + final boolean isJsonResponse = accept != null && accept.toLowerCase().contains("json"); + // extract meta data + // @see PlantUML Code + final FileFormat format = getImageFileFormat(formString, contentType, filename, response); + if (format == null) { + return; // error is already set/handled inside `getImageFileFormat` + } + final Metadata metadata = getMetadata(is, format, response); + if (metadata == null) { + return; // error is already set/handled inside `getMetadata` + } + response.addHeader("Access-Control-Allow-Origin", "*"); + if (isJsonResponse) { + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(metadata.toJson().toString()); + } else { + response.setContentType(FileFormat.UTXT.getMimeType()); + response.getWriter().write(metadata.toString()); + } + } + + /** + * Get the file format from the PlantUML diagram image. + * + * @param format image format passed by the user via the request param `format` + * @param contentType response content type where the PlantUML diagram image is from + * @param filename diagram image file name + * @param response response object to `sendError` including error message + * + * @return PlantUML diagram image format; if unknown format return `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + private FileFormat getImageFileFormat( + String format, String contentType, String filename, HttpServletResponse response + ) throws IOException { + if (format != null && !format.isEmpty()) { + return getImageFileFormatFromFormatString(format, response); + } + if (filename != null && !filename.isEmpty()) { + final FileFormat fileFormat = getImageFileFormatFromFilenameExtension(filename); + if (fileFormat != null) { + return fileFormat; + } + } + if (contentType != null && !contentType.isEmpty()) { + final FileFormat fileFormat = getImageFileFormatFromContentType(contentType); + if (fileFormat != null) { + return fileFormat; + } + } + response.sendError( + HttpServletResponse.SC_BAD_REQUEST, + "PlantUML image format detection failed. Please set \"format\" (format) manually." + ); + return null; + } + + /** + * Get the file format from the PlantUML diagram image based on a format string. + * + * @param format image format passed by the user via the request param `format` + * @param response response object to `sendError` including error message; if `null` no error will be send + * + * @return PlantUML diagram image format; if unknown format return `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + private FileFormat getImageFileFormatFromFormatString( + String format, HttpServletResponse response + ) throws IOException { + switch (format.toLowerCase()) { + case "png": return FileFormat.PNG; + case "svg": return FileFormat.SVG; + default: + if (response != null) { + response.sendError( + HttpServletResponse.SC_BAD_REQUEST, + "The format \"" + format + "\" is not supported for meta data extraction." + ); + } + return null; + } + } + + /** + * Get the file format from the PlantUML diagram image based on the filenames extension. + * + * @param filename PlantUML image file name + * + * @return PlantUML diagram image format; if unknown format return `null` + * + * @throws IOException Can not happend! Will not occur. + */ + private FileFormat getImageFileFormatFromFilenameExtension(String filename) throws IOException { + int extensionPosition = filename.lastIndexOf("."); + if (extensionPosition != -1) { + String extension = filename.substring(extensionPosition + 1); + return getImageFileFormatFromFormatString(extension, null); + } + Logger logger = Logger.getLogger("com.plantuml"); + logger.log(Level.WARNING, "File name \"{0}\" is malformed. Should be: name.extension", filename); + return null; + } + + /** + * Get the file format from the PlantUML diagram image based on the response content type. + * + * @param contentType response content type where the PlantUML diagram image is from + * + * @return PlantUML diagram image format; if unknown content type return `null` + */ + private FileFormat getImageFileFormatFromContentType(String contentType) { + final String ct = contentType.toLowerCase(); + if (ct.contains("png")) { + return FileFormat.PNG; + } + if (ct.contains("svg") || ct.contains("xml")) { + return FileFormat.SVG; + } + Logger logger = Logger.getLogger("com.plantuml"); + logger.log(Level.SEVERE, "Unknown content type \"{0}\" for meta data extraction", contentType); + return null; + } + + /** + * Get meta data from PlantUML diagram image. + * + * @param is PlantUML diagram image input stream + * @param format PlantUML diagram image file format + * @param response response object to `sendError` including error message + * + * @return parsed meta data; on error return `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + private Metadata getMetadata( + InputStream is, FileFormat format, HttpServletResponse response + ) throws IOException { + switch (format) { + case PNG: + return getMetadataFromPNG(is, response); + case SVG: + final String svg; + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + svg = br.lines().collect(Collectors.joining("\n")); + } + return getMetadataFromSVG(svg, response); + default: + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unsupported image format."); + return null; + } + } + + /** + * Get meta data from PNG PlantUML diagram image. + * + * Challenge: PNG meta data is only a single String and contains more than the PlantUML diagram. + * PNG meta data contains: + * 1. decoded PlantUML code + * 2. empty line + * 3. version information + * Notes: + * - in theory the meta data could contain the PlantUML `RawString` as well as the `PlainString` + * but since both are ALWAYS identical (methods to get them are identical), one will ALWAYS dropped. + * @see PlantUML Code + * - version information do not contain any empty lines + * Solution: split meta data at the last occurring empty line the result in + * a. decoded PlantUML diagram + * b. version information + * + * @param is PNG image input stream + * @param response response object to `sendError` including error message + * + * @return parsed meta data; on error return `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + private Metadata getMetadataFromPNG(InputStream is, HttpServletResponse response) throws IOException { + final String rawMetadata = new MetadataTag(is, "plantuml").getData(); + if (rawMetadata == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No meta data found."); + return null; + } + // parse meta data + final Metadata metadata = new Metadata(rawMetadata.trim()); + metadata.decoded = metadata.rawContent.substring(0, metadata.rawContent.lastIndexOf("\n\n")); + metadata.encoded = TranscoderUtil.getDefaultTranscoder().encode(metadata.decoded); + metadata.version = metadata.rawContent.substring(rawMetadata.lastIndexOf("\n\n")).trim(); + // add additionally the encoded plantuml string to raw meta data since it's missing by default + metadata.rawContent = metadata.encoded + "\n\n" + metadata.rawContent; + return metadata; + } + + /** + * Get meta data from SVG PlantUML diagram image. + * @see PlantUML Code + * + * @param svg PlantUML digram in SVG format + * @param response response object to `sendError` including error message + * + * @return parsed meta data; on error return `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + private Metadata getMetadataFromSVG(String svg, HttpServletResponse response) throws IOException { + final Metadata metadata = new Metadata(); + // search for meta data start token + final int idx = svg.lastIndexOf(SvgGraphics.META_HEADER); + if (idx == -1) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No meta data found."); + return null; + } + // search for meta data end token + final String part = svg.substring(idx + SvgGraphics.META_HEADER.length()); + final int idxEnd = part.indexOf("]"); + if (idxEnd == -1) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid meta data: No end token found."); + return null; + } + // parse meta data + metadata.encoded = part.substring(0, idxEnd); + try { + metadata.decoded = TranscoderUtil.getDefaultTranscoderProtected().decode(metadata.encoded); + } catch (NoPlantumlCompressionException ex) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid meta data: PlantUML diagram is corrupted."); + return null; + } + return metadata; + } + + /** + * Helper class to store meta data. + */ + @SuppressWarnings("checkstyle:VisibilityModifier") + private class Metadata { + public String rawContent; + public String decoded; + public String encoded; + public String version; + + Metadata() { } + Metadata(String rawMetadataContent) { + rawContent = rawMetadataContent; + } + + public JsonObject toJson() { + JsonObject metadata = new JsonObject(); + metadata.add("encoded", encoded); + metadata.add("decoded", decoded); + if (version != null && !version.isEmpty()) { + metadata.add("version", version); + } + return metadata; + } + + @Override + public String toString() { + if (rawContent != null && !rawContent.isEmpty()) { + return rawContent; + } + if (version == null || version.isEmpty()) { + return encoded + "\n\n" + decoded; + } + return encoded + "\n\n" + decoded + "\n\n" + version; + } + } +} diff --git a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java index 159d568..ca283fe 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java @@ -80,25 +80,45 @@ public class ProxyServlet extends HttpServlet { return false; } + /** + * Validate external URL. + * + * @param url URL to validate + * @param response response object to `sendError` including error message; if `null` no error will be send + * + * @return valid URL; otherwise `null` + * + * @throws IOException `response.sendError` can result in a `IOException` + */ + public static URL validateURL(String url, HttpServletResponse response) throws IOException { + final URL parsedUrl; + try { + parsedUrl = new URL(url); + } catch (MalformedURLException mue) { + if (response != null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "URL malformed."); + } + return null; + } + // Check if URL is in a forbidden format (e.g. IP-Address) + if (forbiddenURL(url)) { + if (response != null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Forbidden URL format."); + } + return null; + } + return parsedUrl; + } + @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { final String fmt = request.getParameter("fmt"); final String source = request.getParameter("src"); final String index = request.getParameter("idx"); - // Check if the src URL is valid - final URL srcUrl; - try { - srcUrl = new URL(source); - } catch (MalformedURLException mue) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "URL malformed."); - return; - } - - // Check if URL is in a forbidden format (e.g. IP-Address) - if (forbiddenURL(source)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Forbidden URL format."); - return; + final URL srcUrl = validateURL(source, response); + if (srcUrl == null) { + return; // error is already set/handled inside `validateURL` } // generate the response @@ -175,7 +195,7 @@ public class ProxyServlet extends HttpServlet { * * @throws IOException if an input or output exception occurred */ - private HttpURLConnection getConnection(final URL url) throws IOException { + public static HttpURLConnection getConnection(final URL url) throws IOException { final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); String token = System.getenv("HTTP_AUTHORIZATION"); diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index bb69e2f..639c04f 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -219,6 +219,14 @@ /ui-helper/* + + metadataservlet + net.sourceforge.plantuml.servlet.MetadataServlet + + + metadataservlet + /metadata/* + diff --git a/src/main/webapp/resource/test/bob.png b/src/main/webapp/resource/test/bob.png new file mode 100644 index 0000000000000000000000000000000000000000..1eee3fe27b3375ef6bdc7db11bd8d22c6a85e76e GIT binary patch literal 2561 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 J<(FNf{{ytW$lw3~ literal 0 HcmV?d00001 diff --git a/src/main/webapp/resource/test/bob.svg b/src/main/webapp/resource/test/bob.svg new file mode 100644 index 0000000..61af34a --- /dev/null +++ b/src/main/webapp/resource/test/bob.svg @@ -0,0 +1,2 @@ + +BobBobAliceAlicehello \ No newline at end of file diff --git a/src/main/webapp/resource/test2diagrams.txt b/src/main/webapp/resource/test/test2diagrams.txt similarity index 100% rename from src/main/webapp/resource/test2diagrams.txt rename to src/main/webapp/resource/test/test2diagrams.txt diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestMetadata.java b/src/test/java/net/sourceforge/plantuml/servlet/TestMetadata.java new file mode 100644 index 0000000..50a3b2d --- /dev/null +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestMetadata.java @@ -0,0 +1,146 @@ +package net.sourceforge.plantuml.servlet; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.apache.hc.client5.http.entity.mime.ByteArrayBody; +import org.apache.hc.client5.http.entity.mime.ContentBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.core5.http.HttpEntity; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import net.sourceforge.plantuml.json.Json; +import net.sourceforge.plantuml.json.JsonObject; +import net.sourceforge.plantuml.servlet.utils.TestUtils; +import net.sourceforge.plantuml.servlet.utils.WebappTestCase; + + +public class TestMetadata extends WebappTestCase { + + @ParameterizedTest + @CsvSource({ + "png, false, true", + "png, true, true", + "svg, false, false", // Note: PlantUML SVG diagram images do not include version information + "svg, true, false", + }) + public void testBobAliceSampleMetadataProxy( + String format, + boolean isJsonResponse, + boolean hasVersion + ) throws IOException { + final String resourceFilename = "bob." + format; + final URL url = new URL(getServerUrl() + "/metadata?src=" + getTestResourceUrl(resourceFilename)); + checkMetadataResponse(url, isJsonResponse, hasVersion); + } + + @ParameterizedTest + @CsvSource({ + "png, false, true", + "png, true, true", + "svg, false, false", // Note: PlantUML SVG diagram images do not include version information + "svg, true, false", + }) + public void testBobAliceSampleMetadataFileUpload( + String format, + boolean isJsonResponse, + boolean hasVersion + ) throws IOException { + // build file upload request entity + final String resourceFilename = "bob." + format; + final URL resourceUrl = new URL(getTestResourceUrl(resourceFilename)); + final byte[] resource = getContentAsBytes(resourceUrl); + final ContentBody contentPart = new ByteArrayBody(resource, resourceFilename); + final MultipartEntityBuilder requestBuilder = MultipartEntityBuilder.create(); + requestBuilder.addPart("diagram", contentPart); + HttpEntity requestEntity = requestBuilder.build(); + // send request including file + final URL url = new URL(getServerUrl() + "/metadata"); + final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setUseCaches(false); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + if (isJsonResponse) { + conn.setRequestProperty("Accept", "application/json"); + } + conn.setRequestProperty("Cache-Control", "no-cache"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.addRequestProperty("Content-length", Long.toString(requestEntity.getContentLength())); + conn.setRequestProperty("Content-Type", requestEntity.getContentType()); + try (OutputStream out = conn.getOutputStream()) { + requestEntity.writeTo(out); + } + // check response + checkMetadataResponse(conn, isJsonResponse, hasVersion); + } + + private void checkMetadataResponse( + URL url, + boolean isJsonResponse, + boolean hasVersion + ) throws IOException { + final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + if (isJsonResponse) { + conn.setRequestProperty("Accept", "application/json"); + } + checkMetadataResponse(conn, isJsonResponse, hasVersion); + } + + private void checkMetadataResponse( + HttpURLConnection conn, + boolean isJsonResponse, + boolean hasVersion + ) throws IOException { + // Verifies HTTP status code and the Content-Type + Assertions.assertEquals(200, conn.getResponseCode(), "Bad HTTP status received"); + if (isJsonResponse) { + Assertions.assertEquals( + "application/json;charset=utf-8", + conn.getContentType().toLowerCase(), + "Response content type is not JSON or UTF-8" + ); + } else { + Assertions.assertEquals( + "text/plain;charset=utf-8", + conn.getContentType().toLowerCase(), + "Response content type is not plain text or UTF-8" + ); + } + // Get and verify the content + final String content = getContentText(conn); + if (isJsonResponse) { + checkMetadataContent(Json.parse(content).asObject(), hasVersion); + } else { + checkMetadataContent(content, hasVersion); + } + } + + private void checkMetadataContent(String metadata, boolean hasVersion) { + Assertions.assertTrue( + metadata.contains(TestUtils.SEQBOB), + "Meta data does not contain encoded PlantUML diagram code" + ); + Assertions.assertTrue( + metadata.contains(TestUtils.SEQBOBCODE), + "Meta data does not contain decoded PlantUML diagram code" + ); + if (hasVersion) { + Assertions.assertTrue( + metadata.contains("PlantUML version"), + "Meta data does not contain PlantUML version" + ); + } + } + private void checkMetadataContent(JsonObject metadata, boolean hasVersion) { + Assertions.assertEquals(TestUtils.SEQBOB, metadata.get("encoded").asString()); + Assertions.assertEquals(TestUtils.SEQBOBCODE, metadata.get("decoded").asString()); + if (hasVersion) { + Assertions.assertNotNull(metadata.get("version").asString()); + } + } +} diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java b/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java index 54c6599..c5ba81b 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java @@ -12,12 +12,14 @@ import net.sourceforge.plantuml.servlet.utils.WebappTestCase; public class TestOldProxy extends WebappTestCase { + private static final String TEST_RESOURCE = "test2diagrams.txt"; + /** * Verifies the proxified reception of the default Bob and Alice diagram */ @Test public void testDefaultProxy() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy/" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy/" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type @@ -39,7 +41,7 @@ public class TestOldProxy extends WebappTestCase { */ @Test public void testProxyWithFormat() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy/svg/" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy/svg/" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java b/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java index a56ecb5..0142bf3 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java @@ -12,12 +12,14 @@ import net.sourceforge.plantuml.servlet.utils.WebappTestCase; public class TestProxy extends WebappTestCase { + private static final String TEST_RESOURCE = "test2diagrams.txt"; + /** * Verifies the proxified reception of the default Bob and Alice diagram */ @Test public void testDefaultProxy() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy?src=" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy?src=" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type @@ -39,7 +41,7 @@ public class TestProxy extends WebappTestCase { */ @Test public void testProxyWithFormat() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&src=" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&src=" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type @@ -61,7 +63,7 @@ public class TestProxy extends WebappTestCase { */ @Test public void testProxyWithFormatIdx0() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&idx=0&src=" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&idx=0&src=" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type @@ -83,7 +85,7 @@ public class TestProxy extends WebappTestCase { */ @Test public void testProxyWithFormatIdx1() throws IOException { - final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&idx=1&src=" + getTestDiagramUrl()); + final URL url = new URL(getServerUrl() + "/proxy?fmt=svg&idx=1&src=" + getTestResourceUrl(TEST_RESOURCE)); final HttpURLConnection conn = (HttpURLConnection)url.openConnection(); // Analyze response // Verifies HTTP status code and the Content-Type diff --git a/src/test/java/net/sourceforge/plantuml/servlet/utils/WebappTestCase.java b/src/test/java/net/sourceforge/plantuml/servlet/utils/WebappTestCase.java index 3b97d8e..c83ef5d 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/utils/WebappTestCase.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/utils/WebappTestCase.java @@ -50,10 +50,10 @@ public abstract class WebappTestCase { return serverUtils.getServerUrl(); } - public String getTestDiagramUrl() { + public String getTestResourceUrl(String resource) { // NOTE: [Old]ProxyServlet.forbiddenURL do not allow URL with IP-Addresses or localhost. String serverUrl = getServerUrl().replace("/localhost", "/test.localhost"); - return serverUrl + "/resource/test2diagrams.txt"; + return serverUrl + "/resource/test/" + resource; } public String getContentText(final URL url) throws IOException {