changeset 1274:67a3d4c59e19

Main-class attribute get trimmed by default
author Jiri Vanek <jvanek@redhat.com>
date Wed, 02 Dec 2015 09:01:34 +0100
parents a83c72313001
children 51dd6ea0aed0
files ChangeLog NEWS netx/net/sourceforge/jnlp/Parser.java tests/netx/unit/net/sourceforge/jnlp/ParserTest.java
diffstat 4 files changed, 279 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Nov 12 18:03:47 2015 +0100
+++ b/ChangeLog	Wed Dec 02 09:01:34 2015 +0100
@@ -1,3 +1,16 @@
+2015-11-26  Jiri Vanek  <jvanek@redhat.com>
+
+	Main-class attribute get trimmed by default
+	* netx/net/sourceforge/jnlp/Parser.java: declared MAINCLASS to keep main-class
+	constant, declared anyWhiteSpace regex to determine whitespaces. All possible fields
+	made final, hardcoded main-class replaced bu constant. New method getOptionalMainClass
+	wrapper around getMainClass but consuming exception. getMainClass, new method
+	reading MAINCLASS from node and handling it. cleanMainClassAttribute, new method
+	trim value and do checks to die or warn if necessary.
+	* tests/netx/unit/net/sourceforge/jnlp/ParserTest.java: added tests for top level
+	behavior on various spaced main-classes. Now extends NoStdOutErrTest
+	* NEWS: mentioned main-class handling change
+
 2015-11-12  Jiri Vanek  <jvanek@redhat.com>
 
 	Added parser to read ico images
--- a/NEWS	Thu Nov 12 18:03:47 2015 +0100
+++ b/NEWS	Wed Dec 02 09:01:34 2015 +0100
@@ -10,6 +10,9 @@
 
 New in release 1.6.2 (YYYY-MM-DD):
 * all connection restrictions now consider also port
+* NetX
+  - main-class attribute trimmed by default
+  - in strict mode, main-class attribute checked for invalid characters
 
 New in release 1.6.1 (2015-09-11):
 * Enabled Entry-Point attribute check
--- a/netx/net/sourceforge/jnlp/Parser.java	Thu Nov 12 18:03:47 2015 +0100
+++ b/netx/net/sourceforge/jnlp/Parser.java	Wed Dec 02 09:01:34 2015 +0100
@@ -24,6 +24,8 @@
 import java.lang.reflect.Method;
 import java.net.*;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import net.sourceforge.jnlp.SecurityDesc.RequestedPermissionLevel;
 import net.sourceforge.jnlp.UpdateDesc.Check;
@@ -40,6 +42,8 @@
 public final class Parser {
     
     private static String CODEBASE = "codebase";
+    private static String MAINCLASS = "main-class";
+    private static final Pattern anyWhiteSpace = Pattern.compile("\\s");
 
     // defines netx.jnlp.Node class if using Tiny XML or Nano XML
 
@@ -72,29 +76,29 @@
     // constructors
     //
     /** the file reference */
-    private JNLPFile file; // do not use (uninitialized)
+    private final JNLPFile file; // do not use (uninitialized)
 
     /** the root node */
-    private Node root;
+    private final Node root;
 
     /** the specification version */
-    private Version spec;
+    private final Version spec;
 
     /** the base URL that all hrefs are relative to */
-    private URL base;
+    private final URL base;
 
     /** the codebase URL */
     private URL codebase;
 
     /** the file URL */
-    private URL fileLocation;
+    private final URL fileLocation;
 
     /** whether to throw errors on non-fatal errors. */
-    private boolean strict; // if strict==true parses a file with no error then strict==false should also
+    private final boolean strict; // if strict==true parses a file with no error then strict==false should also
 
     /** whether to allow extensions to the JNLP specification */
-    private boolean allowExtensions; // true if extensions to JNLP spec are ok
-
+    private final boolean allowExtensions; // true if extensions to JNLP spec are ok
+    
     /**
      * Create a parser for the JNLP file. If the location
      * parameters is not null it is used as the default codebase
@@ -687,7 +691,7 @@
      */
     private AppletDesc getApplet(Node node) throws ParseException {
         String name = getRequiredAttribute(node, "name", R("PUnknownApplet"));
-        String main = getRequiredAttribute(node, "main-class", null);
+        String main = getMainClass(node, true);
         URL docbase = getURL(node, "documentbase", base);
         Map<String, String> paramMap = new HashMap<>();
         int width = 0;
@@ -718,7 +722,7 @@
      * @throws ParseException if the JNLP file is invalid
      */
     private ApplicationDesc getApplication(Node node) throws ParseException {
-        String main = getAttribute(node, "main-class", null);
+        String main = getMainClass(node, false);
         List<String> argsList = new ArrayList<>();
 
         // if (main == null)
@@ -766,7 +770,7 @@
      * @return the installer descriptor.
      */
     private InstallerDesc getInstaller(Node node) {
-        String main = getAttribute(node, "main-class", null);
+        String main = getOptionalMainClass(node);
 
         return new InstallerDesc(main);
     }
@@ -1339,4 +1343,59 @@
         }
     }
 
+  private String getOptionalMainClass(Node node) {
+        try {
+            return getMainClass(node, false);
+        } catch (ParseException ex) {
+            //only getRequiredAttribute can throw this
+            //and as there is call to getMainClass  with required false
+            //it is not going to be thrown
+            OutputController.getLogger().log(ex);
+            return null;
+        }
+    }
+
+    private String getMainClass(Node node, boolean required) throws ParseException {
+        String main;
+        if (required) {
+            main = getRequiredAttribute(node, MAINCLASS, null);
+        } else {
+            main = getAttribute(node, MAINCLASS, null);
+        }
+        return cleanMainClassAttribute(main);
+    }
+
+    private String cleanMainClassAttribute(String main) throws ParseException {
+        if (main != null) {
+            Matcher matcher = anyWhiteSpace.matcher(main);
+            boolean found = matcher.find();
+            if (found && !strict) {
+                OutputController.getLogger().log(OutputController.Level.WARNING_ALL, "Warning! main-class contains whitespace - '" + main + "'");
+                main = main.trim();
+                OutputController.getLogger().log(OutputController.Level.WARNING_ALL, "Trimmed - '" + main + "'");
+            }
+            boolean valid = true;
+            if (!Character.isJavaIdentifierStart(main.charAt(0))) {
+                valid = false;
+                OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG, "Invlaid char in main-class: '" + main.charAt(0) + "'");
+            }
+            for (int i = 1; i < main.length(); i++) {
+                if (main.charAt(i)=='.'){
+                    //dot connects identifiers
+                    continue;
+                }
+                if (!Character.isJavaIdentifierPart(main.charAt(i))) {
+                    valid = false;
+                    OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG, "Invlaid char in main-class: '" + main.charAt(i) + "'");
+                }
+            }
+            if (!valid) {
+                OutputController.getLogger().log(OutputController.Level.WARNING_ALL, "main-class contains invalid characters - '" + main + "'. Check with vendor.");
+                if (strict) {
+                    throw new ParseException("main-class contains invalid characters - '" + main + "'. Check with vendor. You are in strict mode. This is fatal.");
+                }
+            }
+        }
+        return main;
+    }
 }
--- a/tests/netx/unit/net/sourceforge/jnlp/ParserTest.java	Thu Nov 12 18:03:47 2015 +0100
+++ b/tests/netx/unit/net/sourceforge/jnlp/ParserTest.java	Wed Dec 02 09:01:34 2015 +0100
@@ -44,12 +44,13 @@
 import java.util.Locale;
 
 import net.sourceforge.jnlp.mock.MockJNLPFile;
+import net.sourceforge.jnlp.util.logging.NoStdOutErrTest;
 
 import org.junit.Assert;
 import org.junit.Test;
 
 /** Test various corner cases of the parser */
-public class ParserTest {
+public class ParserTest extends NoStdOutErrTest {
 
     private static final String LANG = "en";
     private static final String COUNTRY = "CA";
@@ -59,6 +60,8 @@
     private static final Locale ALL_LOCALE = new Locale(LANG, COUNTRY, VARIANT);
 
     ParserSettings defaultParser=new ParserSettings();
+    ParserSettings strictParser=new ParserSettings(true, true, true);
+    
     @Test(expected = MissingInformationException.class)
     public void testMissingInfoFullLocale() throws ParseException {
         String data = "<jnlp></jnlp>\n";
@@ -1439,4 +1442,193 @@
         Assert.assertEquals(true, eex != null);
         Assert.assertEquals(true, eex instanceof ParseException);
     }
+    
+    
+    @Test
+    public void testNullMainClassApplication() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc>\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals(null, main1);
+        
+        //strict also ok
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, defaultParser, null);
+        String main2 = parser2.getLauncher(root2).getMainClass();
+        Assert.assertEquals(null, main2);
+
+    }
+    
+    @Test
+    public void testNullMainClassInstaller() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<installer-desc>\n"
+                + "</installer-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals(null, main1);
+        
+        //strict also ok
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        String main2 = parser2.getLauncher(root2).getMainClass();
+        Assert.assertEquals(null, main2);
+
+    }
+    
+      @Test(expected = ParseException.class)
+    public void testNullMainClassApplet() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<applet-desc>\n"
+                + "</applet-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        parser1.getLauncher(root1).getMainClass();
+        //both throw
+    }
+    
+    
+    @Test
+    public void testOkMainClassApplication() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc main-class=\"some.main.class\">\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals("some.main.class", main1);
+        
+        //strict also ok
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        String main2 = parser2.getLauncher(root2).getMainClass();
+        Assert.assertEquals("some.main.class", main2);
+
+    }
+    
+    
+     @Test(expected = ParseException.class)
+    public void testNeedToBeTrimmed1MainClassApplication() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc main-class=\"  some.main.class  \">\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals("some.main.class", main1);
+        
+        //strict throws
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        parser2.getLauncher(root2).getMainClass();
+
+    }
+    
+    @Test(expected = ParseException.class)
+    public void testNeedToBeTrimmed2MainClassApplication() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc main-class=\"\nsome.main.class\t\">\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals("some.main.class", main1);
+        
+        //strict throws
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        parser2.getLauncher(root2).getMainClass();
+
+    }
+    
+    @Test(expected = ParseException.class)
+    public void testSpacesInsidePersistedMainClassApplication() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc main-class=\"\nsom e.main .class\t\">\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals("som e.main .class", main1);
+        
+        //strict throws
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        parser2.getLauncher(root2).getMainClass();
+    }
+    
+    @Test(expected = ParseException.class)
+    public void testSpacesAroundDots() throws Exception {
+        String data = "<?xml version=\"1.0\"?>\n"
+                + "<jnlp codebase=\"http://someNotExistingUrl.com\"  >\n"
+                + "<application-desc main-class=\"\nsome\t.\nanother . main\t.class. here\t\">\n"
+                + "</application-desc>\n"
+                + "</jnlp>";
+
+        Node root1 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), defaultParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root1.getNodeName());
+        MockJNLPFile file1 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser1 = new Parser(file1, null, root1, defaultParser, null);
+        String main1 = parser1.getLauncher(root1).getMainClass();
+        Assert.assertEquals("some . another . main .class. here", main1);
+        
+        //strict throws
+        Node root2 = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()), strictParser);
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root2.getNodeName());
+        MockJNLPFile file2 = new MockJNLPFile(LANG_LOCALE);
+        Parser parser2 = new Parser(file2, null, root2, strictParser, null);
+        parser2.getLauncher(root2).getMainClass();
+    }
+
 }