diff --git a/README.md b/README.md index edc6f61..d58f157 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,12 @@ PlantUML Server is a web application to generate UML diagrams on-the-fly. -[PlantUML is **not** affected by the log4j vulnerability.](https://github.com/plantuml/plantuml/issues/826) +> [PlantUML is **not** affected by the log4j vulnerability.](https://github.com/plantuml/plantuml/issues/826) + +> **Breaking changes**: +> PlantUML Server sets `PLANTUML_SECURITY_PROFILE` to `INTERNET` by default starting with version `v1.2023.9`. +> You can change its behavior back to work like before if you set the environment variable `PLANTUML_SECURITY_PROFILE` to `LEGACY`. +> But before you do that, please take a look to [PlantUMLs Security](https://plantuml.com/security) page. ![PlantUML Server](https://raw.githubusercontent.com/plantuml/plantuml-server/master/docs/screenshot.png) @@ -119,6 +124,7 @@ You can set all the following variables: * Default value: `ROOT` * `PLANTUML_CONFIG_FILE` * Local path to a PlantUML configuration file (identical to the `-config` flag on the CLI) + * File content will be added before each PlantUML diagram code. * Default value: `null` * `PLANTUML_LIMIT_SIZE` * Limits image width and height @@ -135,6 +141,13 @@ You can set all the following variables: * `ALLOW_PLANTUML_INCLUDE` * Enables `!include` processing which can read files from the server into diagrams. Files are read relative to the current working directory. * Default value: `false` +* `PLANTUML_SECURITY_PROFILE` + * Set PlantUML security profile. See [PlantUML security](https://plantuml.com/security). + * Default value: `INTERNET` +* `PLANTUML_PROPERTY_FILE` + * Set PlantUML system properties (like over the Java command line using the `-Dpropertyname=value` syntax). + * To see what kind of file content is supported, see the documentation of [`java.util.Properties.load`](https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#load-java.io.Reader-). + * Default value: `null` ## Alternate: How to build your docker image diff --git a/src/main/java/net/sourceforge/plantuml/servlet/DiagramResponse.java b/src/main/java/net/sourceforge/plantuml/servlet/DiagramResponse.java index f7d2cb1..e43ab14 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/DiagramResponse.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/DiagramResponse.java @@ -48,6 +48,8 @@ import net.sourceforge.plantuml.core.DiagramDescription; import net.sourceforge.plantuml.core.ImageData; import net.sourceforge.plantuml.error.PSystemError; import net.sourceforge.plantuml.preproc.Defines; +import net.sourceforge.plantuml.security.SecurityProfile; +import net.sourceforge.plantuml.security.SecurityUtils; import net.sourceforge.plantuml.version.Version; /** @@ -61,13 +63,18 @@ public class DiagramResponse { */ private static final String POWERED_BY = "PlantUML Version " + Version.versionString(); + /** + * PLANTUML_CONFIG_FILE content. + */ private static final List CONFIG = new ArrayList<>(); + /** + * Cache/flag to ensure that the `init()` method is called only once. + */ + private static boolean initialized = false; + static { - OptionFlags.ALLOW_INCLUDE = false; - if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { - OptionFlags.ALLOW_INCLUDE = true; - } + init(); } /** @@ -96,6 +103,43 @@ public class DiagramResponse { request = req; } + /** + * Initialize PlantUML configurations and properties as well as loading the PlantUML config file. + */ + public static void init() { + if (initialized) { + return; + } + initialized = true; + // set allow include to false by default + OptionFlags.ALLOW_INCLUDE = false; + if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { + OptionFlags.ALLOW_INCLUDE = true; + } + // set security profile to INTERNET by default + // NOTE: this property is cached inside PlantUML and cannot be changed after the first call of PlantUML + System.setProperty("PLANTUML_SECURITY_PROFILE", SecurityProfile.INTERNET.toString()); + if (System.getenv("PLANTUML_SECURITY_PROFILE") != null) { + System.setProperty("PLANTUML_SECURITY_PROFILE", System.getenv("PLANTUML_SECURITY_PROFILE")); + } + // load properties from file + if (System.getenv("PLANTUML_PROPERTY_FILE") != null) { + try (FileReader propertyFileReader = new FileReader(System.getenv("PLANTUML_PROPERTY_FILE"))) { + System.getProperties().load(propertyFileReader); + } catch (IOException e) { + e.printStackTrace(); + } + } + // load PlantUML config file + if (System.getenv("PLANTUML_CONFIG_FILE") != null) { + try (BufferedReader br = new BufferedReader(new FileReader(System.getenv("PLANTUML_CONFIG_FILE")))) { + br.lines().forEach(CONFIG::add); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + /** * Render and send a specific uml diagram. * @@ -108,23 +152,8 @@ public class DiagramResponse { response.addHeader("Access-Control-Allow-Origin", "*"); response.setContentType(getContentType()); - if (CONFIG.size() == 0 && System.getenv("PLANTUML_CONFIG_FILE") != null) { - // Read config - final BufferedReader br = new BufferedReader(new FileReader(System.getenv("PLANTUML_CONFIG_FILE"))); - if (br == null) { - return; - } - try { - String s = null; - while ((s = br.readLine()) != null) { - CONFIG.add(s); - } - } finally { - br.close(); - } - } - - SourceStringReader reader = new SourceStringReader(Defines.createEmpty(), uml, CONFIG); + final Defines defines = getPreProcDefines(); + SourceStringReader reader = new SourceStringReader(defines, uml, CONFIG); if (CONFIG.size() > 0 && reader.getBlocks().get(0).getDiagram().getWarningOrError() != null) { reader = new SourceStringReader(uml); } @@ -156,6 +185,23 @@ public class DiagramResponse { diagram.exportDiagram(response.getOutputStream(), idx, new FileFormatOption(format)); } + /** + * Get PlantUML preprocessor defines. + * + * @return preprocessor defines + */ + private Defines getPreProcDefines() { + final Defines defines; + if (SecurityUtils.getSecurityProfile() == SecurityProfile.UNSECURE) { + // set dirpath to current dir but keep filename and filenameNoExtension undefined + defines = Defines.createWithFileName(new java.io.File("dummy.puml")); + defines.overrideFilename(""); + } else { + defines = Defines.createEmpty(); + } + return defines; + } + /** * Is block uml unmodified? * diff --git a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java index b744f3f..76c2b46 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java @@ -34,7 +34,6 @@ 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; @@ -55,6 +54,12 @@ import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; @SuppressWarnings("SERIAL") public class PlantUmlServlet extends AsciiCoderServlet { + static { + // Initialize the PlantUML server. + // You could say that this is like the `static void main(String[] args)` of the PlantUML server. + DiagramResponse.init(); + } + /** * Default encoded uml text. * Bob -> Alice : hello @@ -66,13 +71,6 @@ public class PlantUmlServlet extends AsciiCoderServlet { 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. * diff --git a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java index ca283fe..345d042 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/ProxyServlet.java @@ -41,7 +41,6 @@ import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.plantuml.BlockUml; import net.sourceforge.plantuml.FileFormat; -import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.SourceStringReader; import net.sourceforge.plantuml.core.Diagram; import net.sourceforge.plantuml.core.UmlSource; @@ -54,13 +53,6 @@ import net.sourceforge.plantuml.core.UmlSource; @SuppressWarnings("SERIAL") public class ProxyServlet extends HttpServlet { - static { - OptionFlags.ALLOW_INCLUDE = false; - if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { - OptionFlags.ALLOW_INCLUDE = true; - } - } - public static boolean forbiddenURL(String full) { if (full == null) { return true; diff --git a/src/main/java/net/sourceforge/plantuml/servlet/UmlDiagramService.java b/src/main/java/net/sourceforge/plantuml/servlet/UmlDiagramService.java index 167bf91..6e77183 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/UmlDiagramService.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/UmlDiagramService.java @@ -34,7 +34,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.plantuml.FileFormat; -import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.servlet.utility.UmlExtractor; import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; @@ -44,13 +43,6 @@ import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor; @SuppressWarnings("SERIAL") public abstract class UmlDiagramService extends HttpServlet { - static { - OptionFlags.ALLOW_INCLUDE = false; - if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { - OptionFlags.ALLOW_INCLUDE = true; - } - } - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { final String url = request.getRequestURI(); diff --git a/src/main/java/net/sourceforge/plantuml/servlet/utility/UmlExtractor.java b/src/main/java/net/sourceforge/plantuml/servlet/utility/UmlExtractor.java index 4287fa3..8f4c8fa 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/utility/UmlExtractor.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/utility/UmlExtractor.java @@ -29,7 +29,6 @@ import java.net.URLDecoder; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileFormatOption; -import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.SourceStringReader; import net.sourceforge.plantuml.code.Transcoder; import net.sourceforge.plantuml.code.TranscoderUtil; @@ -42,13 +41,6 @@ import net.sourceforge.plantuml.core.ImageData; */ public abstract class UmlExtractor { - static { - OptionFlags.ALLOW_INCLUDE = false; - if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) { - OptionFlags.ALLOW_INCLUDE = true; - } - } - /** * Build the complete UML source from the compressed source extracted from the * HTTP URI. diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 639c04f..30b7b65 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -71,6 +71,7 @@ plantumlservlet net.sourceforge.plantuml.servlet.PlantUmlServlet + 0 plantumlservlet