# HG changeset patch # User Jiri Vanek # Date 1449043294 -3600 # Node ID 67a3d4c59e193e3685c6f322951f32be623d7482 # Parent a83c72313001aa3e2283b867eb14737c3107ca9e Main-class attribute get trimmed by default diff -r a83c72313001 -r 67a3d4c59e19 ChangeLog --- 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 + + 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 Added parser to read ico images diff -r a83c72313001 -r 67a3d4c59e19 NEWS --- 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 diff -r a83c72313001 -r 67a3d4c59e19 netx/net/sourceforge/jnlp/Parser.java --- 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 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 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; + } } diff -r a83c72313001 -r 67a3d4c59e19 tests/netx/unit/net/sourceforge/jnlp/ParserTest.java --- 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 = "\n"; @@ -1439,4 +1442,193 @@ Assert.assertEquals(true, eex != null); Assert.assertEquals(true, eex instanceof ParseException); } + + + @Test + public void testNullMainClassApplication() throws Exception { + String data = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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 = "\n" + + "\n" + + "\n" + + "\n" + + ""; + + 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(); + } + }