add code injection filter
doesn't protect against "Expression Language Injection", therefore I've added an schema compiler option to activate the copying of the annotation which is by default off

git-svn-id: https://svn.apache.org/repos/asf/xmlbeans/trunk@1890934 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/xmlbeans/XmlOptions.java b/src/main/java/org/apache/xmlbeans/XmlOptions.java
index 7fd8ed6..4a7be45 100644
--- a/src/main/java/org/apache/xmlbeans/XmlOptions.java
+++ b/src/main/java/org/apache/xmlbeans/XmlOptions.java
@@ -134,6 +134,7 @@
         COMPILE_MDEF_NAMESPACES,
         COMPILE_PARTIAL_TYPESYSTEM,
         COMPILE_PARTIAL_METHODS,
+        COMPILE_ANNOTATION_JAVADOC,
         VALIDATE_ON_SET,
         VALIDATE_TREAT_LAX_AS_SKIP,
         VALIDATE_STRICT,
@@ -1369,6 +1370,7 @@
         return flag != null && flag;
     }
 
+
     public XmlOptions setXPathUseXmlBeans() {
         return setXPathUseSaxon(true);
     }
@@ -1382,6 +1384,26 @@
         return flag != null && flag;
     }
 
+    public XmlOptions setCompileAnnotationAsJavadoc() {
+        return setCompileAnnotationAsJavadoc(true);
+    }
+
+    /**
+     * When generating the schema sources, copy over the schema annotations to the javadoc.
+     * Be aware basic code injection is filtered, but annotation based RCE aren't filtered.
+     * So think twice before activating this on untrusted schemas!
+     *
+     * @param useAnnotationAsJavadoc {@code true} = copy the annotation - defaults to {@code false}
+     */
+    public XmlOptions setCompileAnnotationAsJavadoc(boolean useAnnotationAsJavadoc) {
+        return set(XmlOptionsKeys.COMPILE_ANNOTATION_JAVADOC, useAnnotationAsJavadoc);
+    }
+
+    public boolean isCompileAnnotationAsJavadoc() {
+        Boolean flag = (Boolean) get(XmlOptionsKeys.COMPILE_ANNOTATION_JAVADOC);
+        return flag != null && flag;
+    }
+
     public XmlOptions setAttributeValidationCompatMode(boolean attributeValidationCompatMode) {
         return set(XmlOptionsKeys.ATTTRIBUTE_VALIDATION_COMPAT_MODE, attributeValidationCompatMode);
     }
diff --git a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeCodePrinter.java b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeCodePrinter.java
index 6a891a1..1f14a20 100644
--- a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeCodePrinter.java
+++ b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeCodePrinter.java
@@ -185,7 +185,7 @@
         }
 
         emit("/**");
-        if(sType.getDocumentation() != null && sType.getDocumentation().length() > 0){
+        if (opt.isCompileAnnotationAsJavadoc() && sType.getDocumentation() != null && sType.getDocumentation().length() > 0){
             emit(" *");
             printJavaDocBody(sType.getDocumentation());
             emit(" *");
@@ -500,29 +500,15 @@
         emit(" */");
     }
 
-    //  removes newline, tabs and white spaces.
-    String cleanUpString(String s){
-        s = s.replace("\n", "");
-        s = s.replace("\t", "");
-        return s.trim();
-    }
+    void printJavaDocBody(String doc) throws IOException{
+        // add some poor mans code injection protection
+        // this is not protecting against annotation based RCEs like CVE-2018-16621
+        String docClean = doc.trim()
+            .replace("\t", "")
+            .replace("*/", "* /");
 
-    void printJavaDocBody(String s) throws IOException{
-
-        int start = 0;
-        int newLineIndex = s.indexOf("\n");
-
-        if(newLineIndex == -1){
+        for (String s : docClean.split("[\\n\\r]+")) {
             emit(" * " + s);
-        }else{
-            if(newLineIndex == 0){
-                newLineIndex = s.indexOf("\n", newLineIndex + 1);
-            }
-            while(newLineIndex > 0){
-                emit(" * " + cleanUpString(s.substring(start, newLineIndex)));
-                start = newLineIndex;
-                newLineIndex = s.indexOf("\n", start + 1);
-            }
         }
     }
 
@@ -810,7 +796,7 @@
         boolean xmltype = (javaType == SchemaProperty.XML_OBJECT);
 
         if (prop.extendsJavaSingleton()) {
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                 printJavaDocParagraph(propertyDocumentation);
             }else {
                 printJavaDoc((several ? "Gets first " : "Gets the ") + propdesc, BeanMethod.GET);
@@ -829,7 +815,7 @@
         }
 
         if (prop.extendsJavaOption()) {
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                 printJavaDocParagraph(propertyDocumentation);
             }else {
                 printJavaDoc((several ? "True if has at least one " : "True if has ") + propdesc, BeanMethod.IS_SET);
@@ -838,7 +824,7 @@
         }
 
         if (several) {
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                 printJavaDocParagraph(propertyDocumentation);
             }
 
@@ -849,14 +835,14 @@
                 wrappedType = javaWrappedType(javaType);
             }
 
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                 printJavaDocParagraph(propertyDocumentation);
             }else{
                 printJavaDoc("Gets a List of " + propdesc + "s", BeanMethod.GET_LIST);
             }
             emit("java.util.List<" + wrappedType + "> get" + propertyName + "List();", BeanMethod.GET_LIST);
 
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                 printJavaDocParagraph(propertyDocumentation);
             }else{
                 printJavaDoc("Gets array of all " + propdesc + "s", BeanMethod.GET_ARRAY);
@@ -909,7 +895,7 @@
         String propdesc = "\"" + qName.getLocalPart() + "\"" + (isAttr ? " attribute" : " element");
 
         if (singleton) {
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0) {
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0) {
                 printJavaDocParagraph(propertyDocumentation);
             } else {
                 printJavaDoc((several ? "Sets first " : "Sets the ") + propdesc, BeanMethod.SET);
@@ -933,7 +919,7 @@
         }
 
         if (optional) {
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0) {
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0) {
                 printJavaDocParagraph(propertyDocumentation);
             } else {
                 printJavaDoc((several ? "Removes first " : "Unsets the ") + propdesc, BeanMethod.UNSET);
@@ -944,14 +930,14 @@
         if (several) {
             String arrayName = propertyName + "Array";
 
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0) {
+            if(opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0) {
                 printJavaDocParagraph(propertyDocumentation);
             } else {
                 printJavaDoc("Sets array of all " + propdesc, BeanMethod.SET_ARRAY);
             }
             emit("void set" + arrayName + "(" + type + "[] " + safeVarName + "Array);", BeanMethod.SET_ARRAY);
 
-            if(propertyDocumentation != null && propertyDocumentation.length() > 0) {
+            if (opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0) {
                 printJavaDocParagraph(propertyDocumentation);
             } else {
                 printJavaDoc("Sets ith " + propdesc, BeanMethod.SET_IDX);
@@ -1675,7 +1661,7 @@
         if (prop.extendsJavaSingleton()) {
             if (bmList == null || bmList.contains(BeanMethod.GET)) {
                 // Value getProp()
-                if(propertyDocumentation != null && propertyDocumentation.length() > 0){
+                if(opt.isCompileAnnotationAsJavadoc() && propertyDocumentation != null && propertyDocumentation.length() > 0){
                     printJavaDocParagraph(propertyDocumentation);
                 } else {
                     printJavaDoc((several ? "Gets first " : "Gets the ") + propdesc);
diff --git a/src/main/java/org/apache/xmlbeans/impl/tool/MavenPlugin.java b/src/main/java/org/apache/xmlbeans/impl/tool/MavenPlugin.java
index 1322431..c96042e 100644
--- a/src/main/java/org/apache/xmlbeans/impl/tool/MavenPlugin.java
+++ b/src/main/java/org/apache/xmlbeans/impl/tool/MavenPlugin.java
@@ -172,6 +172,10 @@
     @Parameter( defaultValue = "false" )
     private boolean debug;
 
+    /** copy annotations to javadoc of generated sources - default: false */
+    @Parameter( defaultValue = "false" )
+    private boolean copyAnn;
+
     @Parameter
     private List<Extension> extensions;
 
@@ -269,6 +273,7 @@
             params.setNoUpa(noUpa);
             params.setNoPvr(noPvr);
             params.setNoAnn(noAnn);
+            params.setCopyAnn(copyAnn);
             params.setNoVDoc(noVDoc);
             if (repackage != null && !repackage.isEmpty()) {
                 params.setRepackage("org.apache.xmlbeans.metadata:"+repackage);
diff --git a/src/main/java/org/apache/xmlbeans/impl/tool/Parameters.java b/src/main/java/org/apache/xmlbeans/impl/tool/Parameters.java
index 5bea5f1..9f496a0 100644
--- a/src/main/java/org/apache/xmlbeans/impl/tool/Parameters.java
+++ b/src/main/java/org/apache/xmlbeans/impl/tool/Parameters.java
@@ -53,6 +53,7 @@
     private boolean noVDoc;
     private boolean noExt;
     private boolean debug;
+    private boolean copyAnn;
     private boolean incrementalSrcGen;
     private String repackage;
     private List<Extension> extensions = Collections.emptyList();
@@ -278,6 +279,14 @@
         repackage = newRepackage;
     }
 
+    public boolean isCopyAnn() {
+        return copyAnn;
+    }
+
+    public void setCopyAnn(boolean newCopyAnn) {
+        copyAnn = newCopyAnn;
+    }
+
     public List<Extension> getExtensions() {
         return extensions;
     }
diff --git a/src/main/java/org/apache/xmlbeans/impl/tool/SchemaCompiler.java b/src/main/java/org/apache/xmlbeans/impl/tool/SchemaCompiler.java
index ed6b57b..c824671 100644
--- a/src/main/java/org/apache/xmlbeans/impl/tool/SchemaCompiler.java
+++ b/src/main/java/org/apache/xmlbeans/impl/tool/SchemaCompiler.java
@@ -66,6 +66,7 @@
         System.out.println("    -partialMethods [list] -  comma separated list of bean methods to be generated. Use \"-\" to negate and \"ALL\" for all." );
         System.out.println("                              processed left-to-right, e.g. \"ALL,-GET_LIST\" exclude java.util.List getters - see XmlOptions.BeanMethod" );
         System.out.println("    -repackage - repackage specification, e.g. \"org.apache.xmlbeans.metadata:mypackage.metadata\" to change the metadata directory");
+        System.out.println("    -copyann - copy schema annotations to javadoc (default false) - don't activate on untrusted schema sources!");
         /* Undocumented feature - pass in one schema compiler extension and related parameters
         System.out.println("    -extension - registers a schema compiler extension");
         System.out.println("    -extensionParms - specify parameters for the compiler extension");
@@ -115,6 +116,7 @@
         opts.add("allowmdef");
         opts.add("catalog");
         opts.add("partialMethods");
+        opts.add("copyann");
 
         CommandLine cl = new CommandLine(args, flags, opts);
 
@@ -183,6 +185,7 @@
         boolean noExt = (cl.getOpt("noext") != null);
         boolean nojavac = (cl.getOpt("srconly") != null);
         boolean debug = (cl.getOpt("debug") != null);
+        boolean copyAnn = (cl.getOpt("copyann") != null);
 
         String allowmdef = cl.getOpt("allowmdef");
         Set<String> mdefNamespaces = (allowmdef == null ? Collections.emptySet() :
@@ -337,6 +340,7 @@
         params.setCatalogFile(catString);
         params.setSchemaCodePrinter(codePrinter);
         params.setPartialMethods(parsePartialMethods(partialMethods));
+        params.setCopyAnn(copyAnn);
         boolean result = compile(params);
 
         if (tempdir != null) {
@@ -609,6 +613,7 @@
         boolean noVDoc = params.isNoVDoc();
         boolean noExt = params.isNoExt();
         boolean incrSrcGen = params.isIncrementalSrcGen();
+        boolean copyAnn = params.isCopyAnn();
         Collection<XmlError> outerErrorListener = params.getErrorListener();
         Set<BeanMethod> partialMethods = params.getPartialMethods();
 
@@ -687,6 +692,7 @@
             }
             options.setCompilePartialMethod(partialMethods);
             options.setCompileNoAnnotations(noAnn);
+            options.setCompileAnnotationAsJavadoc(copyAnn);
 
             // save .xsb files
             system.save(filer);
diff --git a/src/main/resources/maven/plugin.xml b/src/main/resources/maven/plugin.xml
index e8a22a3..e9a8f69 100644
--- a/src/main/resources/maven/plugin.xml
+++ b/src/main/resources/maven/plugin.xml
@@ -343,6 +343,13 @@
 					</extensions>
                 ]]></description>
                 </parameter>
+                <parameter>
+                    <name>copyAnn</name>
+                    <type>boolean</type>
+                    <required>false</required>
+                    <editable>true</editable>
+                    <description>copy annotation to javadoc of generated source - don't use on untrusted schemas/sources!</description>
+                </parameter>
             </parameters>
             <configuration>
                 <project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
@@ -360,6 +367,7 @@
                 <noUpa implementation="boolean" default-value="false"/>
                 <noPvr implementation="boolean" default-value="false"/>
                 <noAnn implementation="boolean" default-value="false"/>
+                <copyAnn implementation="boolean" default-value="false"/>
                 <noVDoc implementation="boolean" default-value="false"/>
                 <download implementation="boolean" default-value="false"/>
                 <debug implementation="boolean" default-value="false"/>
diff --git a/src/test/java/compile/scomp/checkin/CompilationTests.java b/src/test/java/compile/scomp/checkin/CompilationTests.java
index c4e5bdc..abe3b24 100644
--- a/src/test/java/compile/scomp/checkin/CompilationTests.java
+++ b/src/test/java/compile/scomp/checkin/CompilationTests.java
@@ -432,6 +432,30 @@
         }
     }
 
+    @Test
+    public void annotation2javadoc() throws Exception {
+        deltree(xbeanOutput("compile/scomp/javadoc"));
+        File srcdir = xbeanOutput("compile/scomp/javadoc/src");
+        File classesdir = xbeanOutput("compile/scomp/javadoc/classes");
+        File outputjar = xbeanOutput("compile/scomp/javadoc/javadoc.jar");
+        Parameters params = new Parameters();
+        params.setXsdFiles(xbeanCase("schemacompiler/javadoc.xsd"));
+        params.setSrcDir(srcdir);
+        params.setClassesDir(classesdir);
+        params.setOutputJar(outputjar);
+        params.setName("javadoc");
+        SchemaCompiler.compile(params);
+
+        Path p = new File(srcdir, "javadoc/RootDocument.java").toPath();
+        String act = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
+        assertFalse(act.contains("* / heck, I'm smart"));
+
+        params.setCopyAnn(true);
+        SchemaCompiler.compile(params);
+
+        act = new String(Files.readAllBytes(p), StandardCharsets.UTF_8);
+        assertTrue(act.contains("* / heck, I'm smart"));
+    }
 
     //TESTENV:
 
diff --git a/src/test/resources/xbean/compile/scomp/schemacompiler/javadoc.xsd b/src/test/resources/xbean/compile/scomp/schemacompiler/javadoc.xsd
new file mode 100755
index 0000000..b682480
--- /dev/null
+++ b/src/test/resources/xbean/compile/scomp/schemacompiler/javadoc.xsd
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Copyright 2004 The Apache Software Foundation
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License. -->
+
+<!DOCTYPE xs:schema [
+    <!ENTITY endcom "*/" >
+    <!ENTITY cve "*/ heck, I'm smart &#10;&#13;*&#47;   &endcom;" >
+]>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+        xmlns="javadoc"
+        targetNamespace="javadoc"
+        elementFormDefault="qualified">
+
+    <xs:element name="root">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="single" minOccurs="0" type="xs:decimal" nillable="true">
+                    <xs:annotation>
+                        <xs:documentation>@deprecated use something else
+                        */ just try to inject code
+                        &cve;
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:element>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>