aboutsummaryrefslogtreecommitdiffstats
path: root/netx/net/sourceforge/jnlp/Parser.java
diff options
context:
space:
mode:
Diffstat (limited to 'netx/net/sourceforge/jnlp/Parser.java')
-rw-r--r--netx/net/sourceforge/jnlp/Parser.java1320
1 files changed, 1320 insertions, 0 deletions
diff --git a/netx/net/sourceforge/jnlp/Parser.java b/netx/net/sourceforge/jnlp/Parser.java
new file mode 100644
index 0000000..9c44f69
--- /dev/null
+++ b/netx/net/sourceforge/jnlp/Parser.java
@@ -0,0 +1,1320 @@
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// Copyright (C) 2009 Red Hat, Inc.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is 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 Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+//import javax.xml.parsers.*; // commented to use right Node
+//import org.w3c.dom.*; // class for using Tiny XML | NanoXML
+//import org.xml.sax.*;
+//import gd.xml.tiny.*;
+import net.sourceforge.jnlp.UpdateDesc.Check;
+import net.sourceforge.jnlp.UpdateDesc.Policy;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.nanoxml.*;
+
+
+/**
+ * Contains methods to parse an XML document into a JNLPFile.
+ * Implements JNLP specification version 1.0.
+ *
+ * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.13 $
+ */
+class Parser {
+
+ private static String R(String key) { return JNLPRuntime.getMessage(key); }
+ private static String R(String key, Object p1) { return R(key, p1, null); }
+ private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); }
+ private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); }
+
+
+ // defines netx.jnlp.Node class if using Tiny XML or Nano XML
+
+ // Currently uses the Nano XML parse. Search for "SAX" or
+ // "TINY" or "NANO" and uncomment those blocks and comment the
+ // active ones (if any) to switch XML parsers. Also
+ // (un)comment appropriate Node class at end of this file and
+ // do a clean build.
+
+ /**
+ * Ensure consistent error handling.
+ */
+ /* SAX
+ static ErrorHandler errorHandler = new ErrorHandler() {
+ public void error(SAXParseException exception) throws SAXParseException {
+ //throw exception;
+ }
+ public void fatalError(SAXParseException exception) throws SAXParseException {
+ //throw exception;
+ }
+ public void warning(SAXParseException exception) {
+ System.err.println("XML parse warning:");
+ exception.printStackTrace();
+ }
+ };
+ */
+
+
+ /** the supported JNLP file versions */
+ private static Version supportedVersions = new Version("1.0 1.5 1.6 6.0");
+
+ // fix: some descriptors need to use the jnlp file at a later
+ // date and having file ref lets us pass it to their
+ // constructors
+ //
+ /** the file reference */
+ private JNLPFile file; // do not use (uninitialized)
+
+ /** the root node */
+ private Node root;
+
+ /** the specification version */
+ private Version spec;
+
+ /** the base URL that all hrefs are relative to */
+ private URL base;
+
+ /** the codebase URL */
+ private URL codebase;
+
+ /** the file URL */
+ private 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
+
+ /** whether to allow extensions to the JNLP specification */
+ private 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
+ * (does not override value of jnlp element's href
+ * attribute).<p>
+ *
+ * The root node may be normalized as a side effect of this
+ * constructor.
+ *
+ * @param file the (uninitialized) file reference
+ * @param base if codebase is not specified, a default base for relative URLs
+ * @param root the root node
+ * @param strict whether to enforce strict compliance with the JNLP spec
+ * @param allowExtensions whether to allow extensions to the JNLP spec
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException {
+ this.file = file;
+ this.root = root;
+ this.strict = strict;
+ this.allowExtensions = allowExtensions;
+
+ // ensure it's a JNLP node
+ if (root == null || !root.getNodeName().equals("jnlp"))
+ throw new ParseException(R("PInvalidRoot"));
+
+ // JNLP tag information
+ this.spec = getVersion(root, "spec", "1.0+");
+ this.codebase = addSlash(getURL(root, "codebase", base));
+ this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase
+ fileLocation = getURL(root, "href", this.base);
+
+ // ensure version is supported
+ if (!supportedVersions.matchesAny(spec))
+ throw new ParseException(R("PSpecUnsupported", supportedVersions));
+
+ // normalize the text nodes
+ root.normalize();
+ }
+
+ /**
+ * Return the JNLP specification versions supported.
+ */
+ public static Version getSupportedVersions() {
+ return supportedVersions;
+ }
+
+ /**
+ * Returns the file version.
+ */
+ public Version getFileVersion() {
+ return getVersion(root, "version", null);
+ }
+
+ /**
+ * Returns the file location.
+ */
+ public URL getFileLocation() {
+ return fileLocation;
+ }
+
+ /**
+ * Returns the codebase.
+ */
+ public URL getCodeBase() {
+ return codebase;
+ }
+
+ /**
+ * Returns the specification version.
+ */
+ public Version getSpecVersion() {
+ return spec;
+ }
+
+ public UpdateDesc getUpdate(Node parent) throws ParseException {
+ UpdateDesc updateDesc = null;
+ Node child = parent.getFirstChild();
+ while (child != null) {
+ if (child.getNodeName().equals("update")) {
+ if (strict && updateDesc != null) {
+ throw new ParseException(R("PTwoUpdates"));
+ }
+
+ Node node = child;
+
+ Check check;
+ String checkValue = getAttribute(node, "check", "timeout");
+ if (checkValue.equals("always")) {
+ check = Check.ALWAYS;
+ } else if (checkValue.equals("timeout")) {
+ check = Check.TIMEOUT;
+ } else if (checkValue.equals("background")) {
+ check = Check.BACKGROUND;
+ } else {
+ check = Check.TIMEOUT;
+ }
+
+ String policyString = getAttribute(node, "policy", "always");
+ Policy policy;
+ if (policyString.equals("always")) {
+ policy = Policy.ALWAYS;
+ } else if (policyString.equals("prompt-update")) {
+ policy = Policy.PROMPT_UPDATE;
+ } else if (policyString.equals("prompt-run")) {
+ policy = Policy.PROMPT_RUN;
+ } else {
+ policy = Policy.ALWAYS;
+ }
+
+ updateDesc = new UpdateDesc(check, policy);
+ }
+
+ child = child.getNextSibling();
+ }
+
+ if (updateDesc == null) {
+ updateDesc = new UpdateDesc(Check.TIMEOUT, Policy.ALWAYS);
+ }
+ return updateDesc;
+ }
+
+ //
+ // This section loads the resources elements
+ //
+
+ /**
+ * Returns all of the ResourcesDesc elements under the specified
+ * node (jnlp or j2se).
+ *
+ * @param parent the parent node (either jnlp or j2se)
+ * @param j2se true if the resources are located under a j2se or java node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public List getResources(Node parent, boolean j2se) throws ParseException {
+ List result = new ArrayList();
+ Node resources[] = getChildNodes(parent, "resources");
+
+ // ensure that there are at least one information section present
+ if (resources.length == 0 && !j2se)
+ throw new ParseException(R("PNoResources"));
+
+ // create objects from the resources sections
+ for (int i=0; i < resources.length; i++)
+ result.add(getResourcesDesc(resources[i], j2se));
+
+ return result;
+ }
+
+ /**
+ * Returns the ResourcesDesc element at the specified node.
+ *
+ * @param node the resources node
+ * @param j2se true if the resources are located under a j2se or java node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException {
+ boolean mainFlag = false; // if found a main tag
+
+ // create resources
+ ResourcesDesc resources =
+ new ResourcesDesc(file,
+ getLocales(node),
+ splitString(getAttribute(node, "os", null)),
+ splitString(getAttribute(node, "arch", null)));
+
+ // step through the elements
+ Node child = node.getFirstChild();
+ while (child != null) {
+ String name = child.getNodeName();
+
+ // check for nativelib but no trusted environment
+ if ("nativelib".equals(name))
+ if (!isTrustedEnvironment())
+ throw new ParseException(R("PUntrustedNative"));
+
+ if ("j2se".equals(name) || "java".equals(name)) {
+ if (getChildNode(root, "component-desc") != null)
+ if (strict)
+ throw new ParseException(R("PExtensionHasJ2SE"));
+ if (!j2se)
+ resources.addResource( getJRE(child) );
+ else
+ throw new ParseException(R("PInnerJ2SE"));
+ }
+
+ if ("jar".equals(name) || "nativelib".equals(name)) {
+ JARDesc jar = getJAR(child);
+
+ // check for duplicate main entries
+ if (jar.isMain()) {
+ if (mainFlag == true)
+ if (strict)
+ throw new ParseException(R("PTwoMains"));
+ mainFlag = true;
+ }
+
+ resources.addResource(jar);
+ }
+
+ if ("extension".equals(name))
+ resources.addResource( getExtension(child) );
+
+ if ("property".equals(name))
+ resources.addResource( getProperty(child) );
+
+ if ("package".equals(name))
+ resources.addResource( getPackage(child) );
+
+ child = child.getNextSibling();
+ }
+
+ return resources;
+ }
+
+ /**
+ * Returns the JRE element at the specified node.
+ *
+ * @param node the j2se/java node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public JREDesc getJRE(Node node) throws ParseException {
+ Version version = getVersion(node, "version", null);
+ URL location = getURL(node, "href", base);
+ String vmArgs = getAttribute(node, "java-vm-args",null);
+ try {
+ checkVMArgs(vmArgs);
+ } catch (IllegalArgumentException argumentException) {
+ vmArgs = null;
+ }
+ String initialHeap = getAttribute(node, "initial-heap-size", null);
+ String maxHeap = getAttribute(node, "max-heap-size", null);
+ List resources = getResources(node, true);
+
+ // require version attribute
+ getRequiredAttribute(node, "version", null);
+
+ return new JREDesc(version, location, vmArgs, initialHeap, maxHeap, resources);
+ }
+
+
+
+ /**
+ * Returns the JAR element at the specified node.
+ *
+ * @param node the jar or nativelib node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public JARDesc getJAR(Node node) throws ParseException {
+ boolean nativeJar = "nativelib".equals(node.getNodeName());
+ URL location = getRequiredURL(node, "href", base);
+ Version version = getVersion(node, "version", null);
+ String part = getAttribute(node, "part", null);
+ boolean main = "true".equals(getAttribute(node, "main", "false"));
+ boolean lazy = "lazy".equals(getAttribute(node, "download", "eager"));
+ int size = Integer.parseInt(getAttribute(node, "size", "0"));
+
+ if (nativeJar && main)
+ if (strict)
+ throw new ParseException(R("PNativeHasMain"));
+
+ return new JARDesc(location, version, part, lazy, main, nativeJar, true);
+
+ }
+
+ /**
+ * Returns the Extension element at the specified node.
+ *
+ * @param node the extension node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public ExtensionDesc getExtension(Node node) throws ParseException {
+ String name = getAttribute(node, "name", null);
+ Version version = getVersion(node, "version", null);
+ URL location = getRequiredURL(node, "href", base);
+
+ ExtensionDesc ext = new ExtensionDesc(name, version, location);
+
+ Node dload[] = getChildNodes(node, "ext-download");
+ for (int i=0; i < dload.length; i++) {
+ boolean lazy = "lazy".equals(getAttribute(dload[i], "download", "eager"));
+
+ ext.addPart(getRequiredAttribute(dload[i], "ext-part", null),
+ getAttribute(dload[i], "part", null),
+ lazy);
+ }
+
+ return ext;
+ }
+
+ /**
+ * Returns the Property element at the specified node.
+ *
+ * @param node the property node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public PropertyDesc getProperty(Node node) throws ParseException {
+ String name = getRequiredAttribute(node, "name", null);
+ String value = getRequiredAttribute(node, "value", "");
+
+ return new PropertyDesc(name, value);
+ }
+
+ /**
+ * Returns the Package element at the specified node.
+ *
+ * @param node the package node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public PackageDesc getPackage(Node node) throws ParseException {
+ String name = getRequiredAttribute(node, "name", null);
+ String part = getRequiredAttribute(node, "part", "");
+ boolean recursive = getAttribute(node, "recursive", "false").equals("true");
+
+ return new PackageDesc(name, part, recursive);
+ }
+
+ //
+ // This section loads the information elements
+ //
+
+ /**
+ * Returns all of the information elements under the specified
+ * node.
+ *
+ * @param parent the parent node (jnlp)
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public List getInfo(Node parent) throws ParseException {
+ List result = new ArrayList();
+ Node info[] = getChildNodes(parent, "information");
+
+ // ensure that there are at least one information section present
+ if (info.length == 0)
+ throw new ParseException(R("PNoInfoElement"));
+
+ // create objects from the info sections
+ for (int i=0; i < info.length; i++)
+ result.add(getInformationDesc(info[i]));
+
+ return result;
+ }
+
+ /**
+ * Returns the information element at the specified node.
+ *
+ * @param node the information node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public InformationDesc getInformationDesc(Node node) throws ParseException {
+ List descriptionsUsed = new ArrayList();
+
+ // locale
+ Locale locales[] = getLocales(node);
+
+ // create information
+ InformationDesc info = new InformationDesc(file, locales);
+
+ // step through the elements
+ Node child = node.getFirstChild();
+ while (child != null) {
+ String name = child.getNodeName();
+
+ if ("title".equals(name))
+ addInfo(info, child, null, getSpanText(child));
+ if ("vendor".equals(name))
+ addInfo(info, child, null, getSpanText(child));
+ if ("description".equals(name)) {
+ String kind = getAttribute(child, "kind", "default");
+ if (descriptionsUsed.contains(kind))
+ if (strict)
+ throw new ParseException(R("PTwoDescriptions", kind));
+
+ descriptionsUsed.add(kind);
+ addInfo(info, child, kind, getSpanText(child));
+ }
+ if ("homepage".equals(name))
+ addInfo(info, child, null, getRequiredURL(child, "href", base));
+ if ("icon".equals(name))
+ addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child));
+ if ("offline-allowed".equals(name))
+ addInfo(info, child, null, Boolean.TRUE);
+ if ("sharing-allowed".equals(name)) {
+ if (strict && !allowExtensions)
+ throw new ParseException(R("PSharing"));
+ addInfo(info, child, null, Boolean.TRUE);
+ }
+ if ("association".equals(name)) {
+ addInfo(info, child, null, getAssociation(child));
+ }
+ if ("shortcut".equals(name)) {
+ addInfo(info, child, null, getShortcut(child));
+ }
+ if ("related-content".equals(name)) {
+ addInfo(info, child, null, getRelatedContent(child));
+ }
+
+ child = child.getNextSibling();
+ }
+
+ return info;
+ }
+
+ /**
+ * Adds a key,value pair to the information object.
+ *
+ * @param info the information object
+ * @param node node name to be used as the key
+ * @param mod key name appended with "-"+mod if not null
+ * @param value the info object to add (icon or string)
+ */
+ protected void addInfo(InformationDesc info, Node node, String mod, Object value) {
+ String modStr = (mod == null) ? "" : "-"+mod;
+
+ if (node == null)
+ return;
+
+ info.addItem(node.getNodeName()+modStr, value);
+ }
+
+ /**
+ * Returns the icon element at the specified node.
+ *
+ * @param node the icon node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public IconDesc getIcon(Node node) throws ParseException {
+ int width = Integer.parseInt(getAttribute(node, "width", "-1"));
+ int height = Integer.parseInt(getAttribute(node, "height", "-1"));
+ int size = Integer.parseInt(getAttribute(node, "size", "-1"));
+ int depth = Integer.parseInt(getAttribute(node, "depth", "-1"));
+ URL location = getRequiredURL(node, "href", base);
+ Object kind = getAttribute(node, "kind", "default");
+
+ return new IconDesc(location, kind, width, height, depth, size);
+ }
+
+ //
+ // This section loads the security descriptor element
+ //
+
+ /**
+ * Returns the security descriptor element. If no security
+ * element was specified in the JNLP file then a SecurityDesc
+ * with applet permissions is returned.
+ *
+ * @param parent the parent node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public SecurityDesc getSecurity(Node parent) throws ParseException {
+ Node nodes[] = getChildNodes(parent, "security");
+
+ // test for too many security elements
+ if (nodes.length > 1)
+ if (strict)
+ throw new ParseException(R("PTwoSecurity"));
+
+ Object type = SecurityDesc.SANDBOX_PERMISSIONS;
+
+ if (nodes.length == 0)
+ type = SecurityDesc.SANDBOX_PERMISSIONS;
+ else if (null != getChildNode(nodes[0], "all-permissions"))
+ type = SecurityDesc.ALL_PERMISSIONS;
+ else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions"))
+ type = SecurityDesc.J2EE_PERMISSIONS;
+ else if (strict)
+ throw new ParseException(R("PEmptySecurity"));
+
+ if (base != null)
+ return new SecurityDesc(file, type, base.getHost());
+ else
+ return new SecurityDesc(file, type, null);
+ }
+
+ /**
+ * Returns whether the JNLP file requests a trusted execution
+ * environment.
+ */
+ protected boolean isTrustedEnvironment() {
+ Node security = getChildNode(root, "security");
+
+ if (security != null)
+ if (getChildNode(security, "all-permissions") != null
+ || getChildNode(security, "j2ee-application-client-permissions") != null)
+ return true;
+
+ return false;
+ }
+
+ //
+ // This section loads the launch descriptor element
+ //
+
+ /**
+ * Returns the launch descriptor element, either AppletDesc,
+ * ApplicationDesc, ComponentDesc, or InstallerDesc.
+ *
+ * @param parent the parent node
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public Object getLauncher(Node parent) throws ParseException {
+ // check for other than one application type
+ if (1 != getChildNodes(parent, "applet-desc").length
+ + getChildNodes(parent, "application-desc").length
+ + getChildNodes(parent, "component-desc").length
+ + getChildNodes(parent, "installer-desc").length)
+ throw new ParseException(R("PTwoDescriptors"));
+
+ Node child = parent.getFirstChild();
+ while (child != null) {
+ String name = child.getNodeName();
+
+ if ("applet-desc".equals(name))
+ return getApplet(child);
+ if ("application-desc".equals(name))
+ return getApplication(child);
+ if ("component-desc".equals(name))
+ return getComponent(child);
+ if ("installer-desc".equals(name))
+ return getInstaller(child);
+
+ child = child.getNextSibling();
+ }
+
+ // not reached
+ return null;
+ }
+
+ /**
+ * Returns the applet descriptor.
+ *
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public AppletDesc getApplet(Node node) throws ParseException {
+ String name = getRequiredAttribute(node, "name", R("PUnknownApplet"));
+ String main = getRequiredAttribute(node, "main-class", null);
+ URL docbase = getURL(node, "documentbase", base);
+ Map paramMap = new HashMap();
+ int width = 0;
+ int height = 0;
+
+ try {
+ width = Integer.parseInt(getRequiredAttribute(node, "width", "100"));
+ height = Integer.parseInt(getRequiredAttribute(node, "height", "100"));
+ }
+ catch (NumberFormatException nfe) {
+ if (width <= 0)
+ throw new ParseException(R("PBadWidth"));
+ throw new ParseException(R("PBadWidth"));
+ }
+
+ // read params
+ Node params[] = getChildNodes(node, "param");
+ for (int i=0; i < params.length; i++) {
+ paramMap.put(getRequiredAttribute(params[i], "name", null),
+ getRequiredAttribute(params[i], "value", ""));
+ }
+
+ return new AppletDesc(name, main, docbase, width, height, paramMap);
+ }
+
+ /**
+ * Returns the application descriptor.
+ *
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public ApplicationDesc getApplication(Node node) throws ParseException {
+ String main = getAttribute(node, "main-class", null);
+ List argsList = new ArrayList();
+
+ // if (main == null)
+ // only ok if can be found in main jar file (can't check here but make a note)
+
+ // read parameters
+ Node args[] = getChildNodes(node, "argument");
+ for (int i=0; i < args.length; i++) {
+ //argsList.add( args[i].getNodeValue() );
+
+ //This approach was not finding the argument text
+ argsList.add( getSpanText(args[i]) );
+ }
+
+ String argStrings[] =
+ (String[]) argsList.toArray( new String[argsList.size()] );
+
+ return new ApplicationDesc(main, argStrings);
+ }
+
+ /**
+ * Returns the component descriptor.
+ */
+ public ComponentDesc getComponent(Node node) {
+ return new ComponentDesc();
+ }
+
+ /**
+ * Returns the installer descriptor.
+ */
+ public InstallerDesc getInstaller(Node node) {
+ String main = getAttribute(node, "main-class", null);
+
+ return new InstallerDesc(main);
+ }
+
+ /**
+ * Returns the association descriptor.
+ */
+ public AssociationDesc getAssociation(Node node) throws ParseException {
+ String[] extensions = getRequiredAttribute(node, "extensions", null).split(" ");
+ String mimeType = getRequiredAttribute(node, "mime-type", null);
+
+ return new AssociationDesc(mimeType, extensions);
+ }
+
+ /**
+ * Returns the shortcut descriptor.
+ */
+ public ShortcutDesc getShortcut(Node node) throws ParseException {
+
+ String online = getAttribute(node, "online", "true");
+ boolean shortcutIsOnline = Boolean.valueOf(online);
+
+ boolean showOnDesktop = false;
+ MenuDesc menu = null;
+
+ // step through the elements
+ Node child = node.getFirstChild();
+ while (child != null) {
+ String name = child.getNodeName();
+
+ if ("desktop".equals(name)) {
+ if (showOnDesktop && strict) {
+ throw new ParseException(R("PTwoDesktops"));
+ }
+ showOnDesktop = true;
+ } else if ("menu".equals(name)){
+ if (menu != null && strict) {
+ throw new ParseException(R("PTwoMenus"));
+ }
+ menu = getMenu(child);
+ }
+
+ child = child.getNextSibling();
+ }
+
+ ShortcutDesc shortcut = new ShortcutDesc(shortcutIsOnline, showOnDesktop);
+ if (menu != null) {
+ shortcut.addMenu(menu);
+ }
+ return shortcut;
+ }
+
+ /**
+ * Returns the menu descriptor.
+ */
+ public MenuDesc getMenu(Node node) {
+ String subMenu = getAttribute(node, "submenu", null);
+
+ return new MenuDesc(subMenu);
+ }
+
+
+ /**
+ * Returns the related-content descriptor.
+ */
+ public RelatedContentDesc getRelatedContent(Node node) throws ParseException {
+
+ getRequiredAttribute(node, "href", null);
+ URL location = getURL(node, "href", base);
+
+ String title = null;
+ String description = null;
+ IconDesc icon = null;
+
+ // step through the elements
+ Node child = node.getFirstChild();
+ while (child != null) {
+ String name = child.getNodeName();
+
+ if ("title".equals(name)) {
+ if (title != null && strict) {
+ throw new ParseException(R("PTwoTitles"));
+ }
+ title = getSpanText(child);
+ } else if ("description".equals(name)) {
+ if (description != null && strict) {
+ throw new ParseException(R("PTwoDescriptions"));
+ }
+ description = getSpanText(child);
+ } else if ("icon".equals(name)) {
+ if (icon != null && strict) {
+ throw new ParseException(R("PTwoIcons"));
+ }
+ icon = getIcon(child);
+ }
+
+ child = child.getNextSibling();
+ }
+
+ RelatedContentDesc relatedContent = new RelatedContentDesc(location);
+ relatedContent.setDescription(description);
+ relatedContent.setIconDesc(icon);
+ relatedContent.setTitle(title);
+
+ return relatedContent;
+
+ }
+
+ // other methods
+
+ /**
+ * Returns an array of substrings seperated by spaces (spaces
+ * escaped with backslash do not separate strings). This method
+ * splits strings as per the spec except that it does replace
+ * escaped other characters with their own value.
+ */
+ public String[] splitString(String source) {
+ if (source == null)
+ return new String[0];
+
+ List result = new ArrayList();
+ StringTokenizer st = new StringTokenizer(source, " ");
+ StringBuffer part = new StringBuffer();
+ while (st.hasMoreTokens()) {
+ part.setLength(0);
+
+ // tack together tokens joined by backslash
+ while (true) {
+ part.append(st.nextToken());
+
+ if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\')
+ part.setCharAt(part.length()-1, ' '); // join with the space
+ else
+ break; // bizarre while format gets \ at end of string right (no extra space added at end)
+ }
+
+ // delete \ quote chars
+ for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop
+ if (part.charAt(i) == '\\')
+ part.deleteCharAt(i--); // and skip previous char so \\ becomes \
+
+ result.add( part.toString() );
+ }
+
+ return (String[]) result.toArray(new String[result.size()] );
+ }
+
+ /**
+ * Returns the Locale object(s) from a node's locale attribute.
+ *
+ * @param node the node with a locale attribute
+ */
+ public Locale[] getLocales(Node node) {
+ List locales = new ArrayList();
+ String localeParts[] =
+ splitString(getAttribute(node, "locale", ""));
+
+ for (int i=0; i < localeParts.length; i++) {
+ Locale l = getLocale( localeParts[i] );
+ if (l != null)
+ locales.add(l);
+ }
+
+ return (Locale[]) locales.toArray(new Locale[locales.size()] );
+ }
+
+ /**
+ * Returns a Locale from a single locale.
+ *
+ * @param locale the locale string
+ */
+ public Locale getLocale(String localeStr) {
+ if (localeStr.length() < 2)
+ return null;
+
+ String language = localeStr.substring(0, 2);
+ String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5);
+ String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8);
+
+ // null is not allowed n locale but "" is
+ return new Locale(language, country, variant);
+ }
+
+
+
+ // XML junk
+
+ /**
+ * Returns the implied text under a node, for example "text" in
+ * "<description>text</description>".
+ *
+ * @param node the node with text under it
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public String getSpanText(Node node) throws ParseException {
+ if (node == null)
+ return null;
+
+ // NANO
+ return node.getNodeValue();
+
+ /* TINY
+ Node child = node.getFirstChild();
+
+ if (child == null) {
+ if (strict)
+ // not sure if this is an error or whether "" is proper
+ throw new ParseException("No text specified (node="+node.getNodeName()+")");
+ else
+ return "";
+ }
+
+ return child.getNodeValue();
+ */
+ }
+
+ /**
+ * Returns the first child node with the specified name.
+ */
+ public static Node getChildNode(Node node, String name) {
+ Node[] result = getChildNodes(node, name);
+ if (result.length == 0)
+ return null;
+ else
+ return result[0];
+ }
+
+ /**
+ * Returns all child nodes with the specified name.
+ */
+ public static Node[] getChildNodes(Node node, String name) {
+ List result = new ArrayList();
+
+ Node child = node.getFirstChild();
+ while (child != null) {
+ if (child.getNodeName().equals(name))
+ result.add(child);
+ child = child.getNextSibling();
+ }
+
+ return (Node[]) result.toArray( new Node[result.size()] );
+ }
+
+
+ /**
+ * Returns a URL with a trailing / appended to it if there is no
+ * trailing slash on the specifed URL.
+ */
+ private URL addSlash(URL source) {
+ if (source == null)
+ return null;
+
+ if (!source.toString().endsWith("/")) {
+ try {
+ source = new URL(source.toString()+"/");
+ }
+ catch (MalformedURLException ex) {
+ }
+ }
+
+ return source;
+ }
+
+
+ /**
+ * Returns the same result as getURL except that a
+ * ParseException is thrown if the attribute is null or empty.
+ *
+ * @param node the node
+ * @param name the attribute containing an href
+ * @param base the base URL
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public URL getRequiredURL(Node node, String name, URL base) throws ParseException {
+ // probably should change "" to null so that url is always
+ // required even if !strict
+ getRequiredAttribute(node, name, "");
+
+ return getURL(node, name, base);
+ }
+
+
+ /**
+ * Returns a URL object from a href string relative to the
+ * code base. If the href denotes a relative URL, it must
+ * reference a location that is a subdirectory of the
+ * codebase.<p>
+ *
+ * @param node the node
+ * @param name the attribute containing an href
+ * @param base the base URL
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public URL getURL(Node node, String name, URL base) throws ParseException {
+ String href = getAttribute(node, name, null);
+ if (href == null)
+ return null; // so that code can throw an exception if attribute was required
+
+ try {
+ if (base == null)
+ return new URL(href);
+ else {
+ try {
+ return new URL(href);
+ }
+ catch (MalformedURLException ex) {
+ // is relative
+ }
+
+ URL result = new URL(base, href);
+
+ // check for going above the codebase
+ if (! result.toString().startsWith( base.toString()) )
+ if (strict)
+ throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base));
+
+ return result;
+ }
+
+ }
+ catch (MalformedURLException ex) {
+ if (base == null)
+ throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href));
+ else
+ throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base));
+ }
+ }
+
+ /**
+ * Returns a Version from the specified attribute and default
+ * value.
+ *
+ * @param node the node
+ * @param name the attribute
+ * @param defaultValue default if no such attribute
+ * @return a Version, or null if no such attribute and default is null
+ */
+ public Version getVersion(Node node, String name, String defaultValue) {
+ String version = getAttribute(node, name, defaultValue);
+ if (version == null)
+ return null;
+ else
+ return new Version(version);
+ }
+
+ /**
+ * Check that the VM args are valid and safe
+ * @param vmArgs a string containing the args
+ * @throws ParseException if the VM arguments are invalid or dangerous
+ */
+ private void checkVMArgs(String vmArgs) throws IllegalArgumentException {
+ if (vmArgs == null) {
+ return;
+ }
+
+ List<String> validArguments = Arrays.asList(getValidVMArguments());
+ List<String> validStartingArguments = Arrays.asList(getValidStartingVMArguments());
+
+ String[] arguments = vmArgs.split(" ");
+ boolean argumentIsValid = false;
+ for (String argument: arguments) {
+ argumentIsValid = false;
+
+ if (validArguments.contains(argument)) {
+ argumentIsValid = true;
+ } else {
+ for (String validStartingArgument: validStartingArguments) {
+ if (argument.startsWith(validStartingArgument)) {
+ argumentIsValid = true;
+ break;
+ }
+ }
+ }
+
+ if (!argumentIsValid) {
+ throw new IllegalArgumentException(argument);
+ }
+ }
+
+ }
+
+ /**
+ * Returns an array of valid (ie safe and supported) arguments for the JVM
+ *
+ * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
+ */
+ private String[] getValidVMArguments() {
+ return new String[] {
+ "-d32", /* use a 32-bit data model if available */
+ "-client", /* to select the client VM */
+ "-server", /* to select the server VM */
+ "-verbose", /* enable verbose output */
+ "-version", /* print product version and exit */
+ "-showversion", /* print product version and continue */
+ "-help", /* print this help message */
+ "-X", /* print help on non-standard options */
+ "-ea", /* enable assertions */
+ "-enableassertions", /* enable assertions */
+ "-da", /* disable assertions */
+ "-disableassertions", /* disable assertions */
+ "-esa", /* enable system assertions */
+ "-enablesystemassertions", /* enable system assertions */
+ "-dsa", /* disable system assertione */
+ "-disablesystemassertions", /* disable system assertione */
+ "-Xmixed", /* mixed mode execution (default) */
+ "-Xint", /* interpreted mode execution only */
+ "-Xnoclassgc", /* disable class garbage collection */
+ "-Xincgc", /* enable incremental garbage collection */
+ "-Xbatch", /* disable background compilation */
+ "-Xprof", /* output cpu profiling data */
+ "-Xdebug", /* enable remote debugging */
+ "-Xfuture", /* enable strictest checks, anticipating future default */
+ "-Xrs", /* reduce use of OS signals by Java/VM (see documentation) */
+ "-XX:+ForceTimeHighResolution", /* use high resolution timer */
+ "-XX:-ForceTimeHighResolution", /* use low resolution (default) */
+ };
+ }
+
+ /**
+ * Returns an array containing the starts of valid (ie safe and supported)
+ * arguments for the JVM
+ *
+ * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
+ */
+ private String[] getValidStartingVMArguments() {
+ return new String[] {
+ "-ea", /* enable assertions for classes */
+ "-enableassertions", /* enable assertions for classes */
+ "-da", /* disable assertions for classes */
+ "-disableassertions", /* disable assertions for classes */
+ "-verbose", /* enable verbose output */
+ "-Xms", /* set initial Java heap size */
+ "-Xmx", /* set maximum Java heap size */
+ "-Xss", /* set java thread stack size */
+ "-XX:NewRatio", /* set Ratio of new/old gen sizes */
+ "-XX:NewSize", /* set initial size of new generation */
+ "-XX:MaxNewSize", /* set max size of new generation */
+ "-XX:PermSize", /* set initial size of permanent gen */
+ "-XX:MaxPermSize", /* set max size of permanent gen */
+ "-XX:MaxHeapFreeRatio", /* heap free percentage (default 70) */
+ "-XX:MinHeapFreeRatio", /* heap free percentage (default 40) */
+ "-XX:UseSerialGC", /* use serial garbage collection */
+ "-XX:ThreadStackSize", /* thread stack size (in KB) */
+ "-XX:MaxInlineSize", /* set max num of bytecodes to inline */
+ "-XX:ReservedCodeCacheSize", /* Reserved code cache size (bytes) */
+ "-XX:MaxDirectMemorySize",
+
+ };
+ }
+
+ /**
+ * Returns the same result as getAttribute except that if strict
+ * mode is enabled or the default value is null a parse
+ * exception is thrown instead of returning the default value.
+ *
+ * @param node the node
+ * @param name the attribute
+ * @param defaultValue default value
+ * @throws ParseException if the attribute does not exist or is empty
+ */
+ public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException {
+ String result = getAttribute(node, name, null);
+
+ if (result == null || result.length() == 0)
+ if (strict || defaultValue == null)
+ throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name));
+
+ if (result == null)
+ return defaultValue;
+ else
+ return result;
+ }
+
+ /**
+ * Retuns an attribute or the specified defaultValue if there is
+ * no such attribute.
+ *
+ * @param node the node
+ * @param name the attribute
+ * @param defaultValue default if no such attribute
+ */
+ public String getAttribute(Node node, String name, String defaultValue) {
+ // SAX
+ // String result = ((Element) node).getAttribute(name);
+ String result = node.getAttribute(name);
+
+ if (result == null || result.length()==0)
+ return defaultValue;
+
+ return result;
+ }
+
+ /**
+ * Return the root node from the XML document in the specified
+ * input stream.
+ *
+ * @throws ParseException if the JNLP file is invalid
+ */
+ public static Node getRootNode(InputStream input) throws ParseException {
+ try {
+ /* SAX
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setErrorHandler(errorHandler);
+
+ Document doc = builder.parse(input);
+ return doc.getDocumentElement();
+ */
+
+ /* TINY
+ Node document = new Node(TinyParser.parseXML(input));
+ Node jnlpNode = getChildNode(document, "jnlp"); // skip comments
+ */
+
+ //A BufferedInputStream is used to allow marking and reseting
+ //of a stream.
+ BufferedInputStream bs = new BufferedInputStream(input);
+
+ /* NANO */
+ final XMLElement xml = new XMLElement();
+ final PipedInputStream pin = new PipedInputStream();
+ final PipedOutputStream pout = new PipedOutputStream(pin);
+ final InputStreamReader isr = new InputStreamReader(bs, getEncoding(bs));
+ // Clean the jnlp xml file of all comments before passing
+ // it to the parser.
+ new Thread(
+ new Runnable(){
+ public void run(){
+ (new XMLElement()).sanitizeInput(isr, pout);
+ try {
+ pout.close();
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ }
+ ).start();
+ xml.parseFromReader(new InputStreamReader(pin));
+ Node jnlpNode = new Node(xml);
+ return jnlpNode;
+ }
+ catch(Exception ex) {
+ throw new ParseException(R("PBadXML"), ex);
+ }
+ }
+
+ /**
+ * Returns the name of the encoding used in this InputStream.
+ *
+ * @param input the InputStream
+ * @return a String representation of encoding
+ */
+ private static String getEncoding(InputStream input) throws IOException{
+ //Fixme: This only recognizes UTF-8, UTF-16, and
+ //UTF-32, which is enough to parse the prolog portion of xml to
+ //find out the exact encoding (if it exists). The reason being
+ //there could be other encodings, such as ISO 8859 which is 8-bits
+ //but it supports latin characters.
+ //So what needs to be done is to parse the prolog and retrieve
+ //the exact encoding from it.
+
+ int[] s = new int[4];
+ String encoding = "UTF-8";
+
+ //Determine what the first four bytes are and store
+ //them into an int array.
+ input.mark(4);
+ for (int i = 0; i < 4; i++) {
+ s[i] = input.read();
+ }
+ input.reset();
+
+ //Set the encoding base on what the first four bytes of the
+ //inputstream turn out to be (following the information from
+ //www.w3.org/TR/REC-xml/#sec-guessing).
+ if (s[0] == 255) {
+ if (s[1] == 254) {
+ if (s[2] != 0 || s[3] != 0) {
+ encoding = "UnicodeLittle";
+ } else {
+ encoding = "X-UTF-32LE-BOM";
+ }
+ }
+ } else if (s[0] == 254 && s[1] == 255 && (s[2] != 0 ||
+ s[3] != 0)) {
+ encoding = "UTF-16";
+
+ } else if (s[0] == 0 && s[1] == 0 && s[2] == 254 &&
+ s[3] == 255) {
+ encoding = "X-UTF-32BE-BOM";
+
+ } else if (s[0] == 0 && s[1] == 0 && s[2] == 0 &&
+ s[3] == 60) {
+ encoding = "UTF-32BE";
+
+ } else if (s[0] == 60 && s[1] == 0 && s[2] == 0 &&
+ s[3] == 0) {
+ encoding = "UTF-32LE";
+
+ } else if (s[0] == 0 && s[1] == 60 && s[2] == 0 &&
+ s[3] == 63) {
+ encoding = "UTF-16BE";
+ } else if (s[0] == 60 && s[1] == 0 && s[2] == 63 &&
+ s[3] == 0) {
+ encoding = "UTF-16LE";
+ }
+
+ return encoding;
+ }
+
+}