diff --git a/src/main/java/net/sourceforge/plantuml/servlet/OldProxyServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/OldProxyServlet.java new file mode 100644 index 0000000..0982b44 --- /dev/null +++ b/src/main/java/net/sourceforge/plantuml/servlet/OldProxyServlet.java @@ -0,0 +1,119 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * Project Info: http://plantuml.sourceforge.net + * + * 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.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import HTTPClient.CookieModule; +import HTTPClient.HTTPConnection; +import HTTPClient.HTTPResponse; +import HTTPClient.ModuleException; +import HTTPClient.ParseException; + +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.SourceStringReader; + +/* + * Proxy servlet of the webapp. + * This servlet retrieves the diagram source of a web resource (web html page) + * and renders it. + */ +@SuppressWarnings("serial") +public class OldProxyServlet extends HttpServlet { + + private static final Pattern proxyPattern = Pattern.compile("/\\w+/proxy/((\\d+)/)?((\\w+)/)?(http://.*)"); + private String format; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + final String uri = request.getRequestURI(); + + Matcher proxyMatcher = proxyPattern.matcher(uri); + if (proxyMatcher.matches()) { + String num = proxyMatcher.group(2); // Optional number of the diagram source + format = proxyMatcher.group(4); // Expected format of the generated diagram + String sourceURL = proxyMatcher.group(5); + handleImageProxy(response, num, sourceURL); + } else { + request.setAttribute("net.sourceforge.plantuml.servlet.decoded", "ERROR Invalid proxy syntax : " + uri); + request.removeAttribute("net.sourceforge.plantuml.servlet.encoded"); + + // forward to index.jsp + RequestDispatcher dispatcher = request.getRequestDispatcher("/index.jsp"); + dispatcher.forward(request, response); + } + } + + private void handleImageProxy(HttpServletResponse response, String num, String source) throws IOException { + SourceStringReader reader = new SourceStringReader(getSource(source)); + int n = num == null ? 0 : Integer.parseInt(num); + + reader.generateImage(response.getOutputStream(), n, new FileFormatOption(getOutputFormat(), false)); + } + + private String getSource(String uri) throws IOException { + CookieModule.setCookiePolicyHandler(null); + + final Pattern p = Pattern.compile("http://[^/]+(/?.*)"); + final Matcher m = p.matcher(uri); + if (m.find() == false) { + throw new IOException(uri); + } + final URL url = new URL(uri); + final HTTPConnection httpConnection = new HTTPConnection(url); + try { + final HTTPResponse resp = httpConnection.Get(m.group(1)); + return resp.getText(); + } catch (ModuleException e) { + throw new IOException(e.toString()); + } catch (ParseException e) { + throw new IOException(e.toString()); + } + } + + private FileFormat getOutputFormat() { + if (format == null) { + return FileFormat.PNG; + } + if (format.equals("svg")) { + return FileFormat.SVG; + } + if (format.equals("txt")) { + return FileFormat.ATXT; + } + return FileFormat.PNG; + } + +} diff --git a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java index bb78e8d..ff4b590 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java @@ -23,26 +23,30 @@ */ package net.sourceforge.plantuml.servlet; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import HTTPClient.CookieModule; -import HTTPClient.HTTPConnection; -import HTTPClient.HTTPResponse; -import HTTPClient.ModuleException; -import HTTPClient.ParseException; - +import net.sourceforge.plantuml.BlockUml; import net.sourceforge.plantuml.FileFormat; -import net.sourceforge.plantuml.FileFormatOption; import net.sourceforge.plantuml.SourceStringReader; +import net.sourceforge.plantuml.core.Diagram; +import net.sourceforge.plantuml.core.UmlSource; + +import java.security.cert.Certificate; +import java.util.List; + +import javax.imageio.IIOException; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; /* * Proxy servlet of the webapp. @@ -52,55 +56,65 @@ import net.sourceforge.plantuml.SourceStringReader; @SuppressWarnings("serial") public class ProxyServlet extends HttpServlet { - private static final Pattern proxyPattern = Pattern.compile("/\\w+/proxy/((\\d+)/)?((\\w+)/)?(http://.*)"); private String format; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - final String uri = request.getRequestURI(); - - Matcher proxyMatcher = proxyPattern.matcher(uri); - if (proxyMatcher.matches()) { - String num = proxyMatcher.group(2); // Optional number of the diagram source - format = proxyMatcher.group(4); // Expected format of the generated diagram - String sourceURL = proxyMatcher.group(5); - handleImageProxy(response, num, sourceURL); - } else { - request.setAttribute("net.sourceforge.plantuml.servlet.decoded", "ERROR Invalid proxy syntax : " + uri); - request.removeAttribute("net.sourceforge.plantuml.servlet.encoded"); - - // forward to index.jsp - RequestDispatcher dispatcher = request.getRequestDispatcher("/index.jsp"); - dispatcher.forward(request, response); - } - } - - private void handleImageProxy(HttpServletResponse response, String num, String source) throws IOException { - SourceStringReader reader = new SourceStringReader(getSource(source)); - int n = num == null ? 0 : Integer.parseInt(num); - - reader.generateImage(response.getOutputStream(), n, new FileFormatOption(getOutputFormat(), false)); - } - - private String getSource(String uri) throws IOException { - CookieModule.setCookiePolicyHandler(null); - - final Pattern p = Pattern.compile("http://[^/]+(/?.*)"); - final Matcher m = p.matcher(uri); - if (m.find() == false) { - throw new IOException(uri); - } - final URL url = new URL(uri); - final HTTPConnection httpConnection = new HTTPConnection(url); + final String source = request.getParameter("src"); + final String index = request.getParameter("idx"); + final URL srcUrl; + // Check if the src URL is valid try { - final HTTPResponse resp = httpConnection.Get(m.group(1)); - return resp.getText(); - } catch (ModuleException e) { - throw new IOException(e.toString()); - } catch (ParseException e) { - throw new IOException(e.toString()); + srcUrl = new URL(source); + } catch (MalformedURLException mue) { + mue.printStackTrace(); + return; + } + + // generate the response + String diagmarkup = getSource(srcUrl); + System.out.println("getSource=>" + diagmarkup); + SourceStringReader reader = new SourceStringReader(diagmarkup); + int n = index == null ? 0 : Integer.parseInt(index); + List blocks = reader.getBlocks(); + BlockUml block = blocks.get(n); + Diagram diagram = block.getDiagram(); + UmlSource umlSrc = diagram.getSource(); + String uml = umlSrc.getPlainString(); + System.out.println("uml="+uml); + + // generate the response + DiagramResponse dr = new DiagramResponse(response, getOutputFormat()); + try { + dr.sendDiagram(uml); + } catch (IIOException iioe) { + // Browser has closed the connection, so the HTTP OutputStream is closed + // Silently catch the exception to avoid annoying log } + dr = null; + } + + private String getSource(URL url) throws IOException { + String line; + BufferedReader rd; + StringBuilder sb; + try { + HttpURLConnection con = getConnection(url); + rd = new BufferedReader(new InputStreamReader(con.getInputStream())); + sb = new StringBuilder(); + + while ((line = rd.readLine()) != null) { + sb.append(line + '\n'); + } + rd.close(); + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + } finally{ + rd = null; + } + return ""; } private FileFormat getOutputFormat() { @@ -111,9 +125,53 @@ public class ProxyServlet extends HttpServlet { return FileFormat.SVG; } if (format.equals("txt")) { - return FileFormat.ATXT; + return FileFormat.UTXT; } return FileFormat.PNG; } + private HttpURLConnection getConnection(URL url) throws IOException { + if (url.getProtocol().startsWith("https")) { + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setReadTimeout(10000); // 10 seconds + // printHttpsCert(con); + con.connect(); + return con; + } else { + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setReadTimeout(10000); // 10 seconds + con.connect(); + return con; + } + } + + /** + * Debug method used to dump the certificate info + * @param con the https connection + */ + private void printHttpsCert(HttpsURLConnection con) { + if (con != null) { + try { + System.out.println("Response Code : " + con.getResponseCode()); + System.out.println("Cipher Suite : " + con.getCipherSuite()); + System.out.println("\n"); + + Certificate[] certs = con.getServerCertificates(); + for (Certificate cert : certs) { + System.out.println("Cert Type : " + cert.getType()); + System.out.println("Cert Hash Code : " + cert.hashCode()); + System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm()); + System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat()); + System.out.println("\n"); + } + + } catch (SSLPeerUnverifiedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } } diff --git a/src/main/java/net/sourceforge/plantuml/servlet/diagrams.txt b/src/main/java/net/sourceforge/plantuml/servlet/diagrams.txt index 2b71c98..89afbc0 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/diagrams.txt +++ b/src/main/java/net/sourceforge/plantuml/servlet/diagrams.txt @@ -22,7 +22,8 @@ UmlDiagramService <|-- PngServlet UmlDiagramService <|-- SvgServlet UmlDiagramService <|-- AsciiServlet UmlDiagramService o- DiagramResponse -MapServlet o- DiagramResponse +MapServlet o-- DiagramResponse +ProxyServlet o-- DiagramResponse @enduml ## Sequence diagram ## diff --git a/src/main/java/net/sourceforge/plantuml/servlet/package.html b/src/main/java/net/sourceforge/plantuml/servlet/package.html index c3c9bb3..f933bbe 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/package.html +++ b/src/main/java/net/sourceforge/plantuml/servlet/package.html @@ -6,7 +6,7 @@ - Service servlets : ImgServlet, SvgServlet, AsciiServlet, ProxyServlet that only produce a diagram as output.

Structure of the service part of the PlantUmlServer:
- +

diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 0b0e839..222fc12 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -31,6 +31,10 @@ proxyservlet net.sourceforge.plantuml.servlet.ProxyServlet + + oldproxyservlet + net.sourceforge.plantuml.servlet.OldProxyServlet + mapservlet net.sourceforge.plantuml.servlet.MapServlet @@ -69,9 +73,13 @@ /start/* - proxyservlet + oldproxyservlet /proxy/* + + proxyservlet + /proxy + java.lang.Throwable /error.jsp diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index a4107a4..4d4f716 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -73,11 +73,6 @@

- <%-- FOOTER --%> <%@ include file="footer.jspf" %> diff --git a/src/main/webapp/resource/test2diagrams.txt b/src/main/webapp/resource/test2diagrams.txt new file mode 100644 index 0000000..77b54f2 --- /dev/null +++ b/src/main/webapp/resource/test2diagrams.txt @@ -0,0 +1,9 @@ +This file is used by the TestProxy unit test. It contains 2 diagrams description. + +@startuml +Bob -> Alice : hello +@enduml + +@startuml +version +@enduml \ No newline at end of file diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java b/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java new file mode 100644 index 0000000..e304ee1 --- /dev/null +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestOldProxy.java @@ -0,0 +1,67 @@ +package net.sourceforge.plantuml.servlet; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import com.meterware.httpunit.GetMethodWebRequest; +import com.meterware.httpunit.WebConversation; +import com.meterware.httpunit.WebForm; +import com.meterware.httpunit.WebRequest; +import com.meterware.httpunit.WebResponse; + +public class TestOldProxy extends WebappTestCase { + /** + * Verifies the proxified reception of the default Bob and Alice diagram + */ + public void testDefaultProxy() throws Exception { + WebConversation conversation = new WebConversation(); + WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/" + getServerUrl() + "resource/test2diagrams.txt"); + WebResponse response = conversation.getResource(request); + // Analyze response + // Verifies the Content-Type header + // assertEquals( "Response content type is not PNG", "image/png", response.getContentType()); + // Get the image and verify its size (~2000 bytes) + InputStream responseStream = response.getInputStream(); + ByteArrayOutputStream imageStream = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int n = 0; + while ((n = responseStream.read(buf)) != -1) { + imageStream.write(buf, 0, n); + } + imageStream.close(); + responseStream.close(); + byte[] inMemoryImage = imageStream.toByteArray(); + int diagramLen = inMemoryImage.length; + assertTrue(diagramLen > 1500); + assertTrue(diagramLen < 2500); + } + + public void testProxyWithFormat() throws Exception { + WebConversation conversation = new WebConversation(); + WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/svg/" + getServerUrl() + "resource/test2diagrams.txt"); + WebResponse response = conversation.getResource(request); + // Analyze response + // Verifies the Content-Type header + // TODO assertEquals( "Response content type is not SVG", "image/svg+xml", response.getContentType()); + // Get the content and verify its size + String diagram = response.getText(); + int diagramLen = diagram.length(); + assertTrue(diagramLen > 1000); + assertTrue(diagramLen < 3000); + } + + /** + * Verifies that the HTTP header of a diagram incites the browser to cache it. + */ + public void testInvalidUrl() throws Exception { + WebConversation conversation = new WebConversation(); + // Try to proxify an invalid address + WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/invalidURL"); + WebResponse response = conversation.getResource(request); + // Analyze response, it must be the empty form + // Verifies the Content-Type header + assertEquals("Response content type is not HTML", "text/html", response.getContentType()); + WebForm forms[] = response.getForms(); + assertEquals(2, forms.length); + } +} diff --git a/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java b/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java index 4a6d7fc..1640723 100644 --- a/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java +++ b/src/test/java/net/sourceforge/plantuml/servlet/TestProxy.java @@ -5,7 +5,6 @@ import java.io.InputStream; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; -import com.meterware.httpunit.WebForm; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; @@ -15,11 +14,12 @@ public class TestProxy extends WebappTestCase { */ public void testDefaultProxy() throws Exception { WebConversation conversation = new WebConversation(); - WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/" + getServerUrl() + "welcome"); + WebRequest request = new GetMethodWebRequest(getServerUrl() + + "proxy?src=" + getServerUrl() + "resource/test2diagrams.txt"); WebResponse response = conversation.getResource(request); // Analyze response // Verifies the Content-Type header - // assertEquals( "Response content type is not PNG", "image/png", response.getContentType()); + assertEquals( "Response content type is not PNG", "image/png", response.getContentType()); // Get the image and verify its size (~2000 bytes) InputStream responseStream = response.getInputStream(); ByteArrayOutputStream imageStream = new ByteArrayOutputStream(); @@ -35,14 +35,15 @@ public class TestProxy extends WebappTestCase { assertTrue(diagramLen > 1500); assertTrue(diagramLen < 2500); } - +/* public void testProxyWithFormat() throws Exception { WebConversation conversation = new WebConversation(); - WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/svg/" + getServerUrl() + "welcome"); + WebRequest request = new GetMethodWebRequest(getServerUrl() + + "proxy?format=svg&src=" + getServerUrl() + "resource/test2diagrams.txt"); WebResponse response = conversation.getResource(request); // Analyze response // Verifies the Content-Type header - // TODO assertEquals( "Response content type is not SVG", "image/svg+xml", response.getContentType()); + assertEquals( "Response content type is not SVG", "image/svg+xml", response.getContentType()); // Get the content and verify its size String diagram = response.getText(); int diagramLen = diagram.length(); @@ -56,12 +57,9 @@ public class TestProxy extends WebappTestCase { public void testInvalidUrl() throws Exception { WebConversation conversation = new WebConversation(); // Try to proxify an invalid address - WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy/invalidURL"); + WebRequest request = new GetMethodWebRequest(getServerUrl() + "proxy?src=invalidURL"); WebResponse response = conversation.getResource(request); - // Analyze response, it must be the empty form - // Verifies the Content-Type header - assertEquals("Response content type is not HTML", "text/html", response.getContentType()); - WebForm forms[] = response.getForms(); - assertEquals(2, forms.length); + // Analyze response, it must be HTTP error 500 + assertEquals("Bad HTTP status received", 500, response.getResponseCode()); } }