--- /dev/null 2018-10-22 10:24:25.000000000 -0400 +++ new/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/XMLFormat.java 2018-10-22 10:24:23.101351600 -0400 @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.net.URL; +import java.util.Arrays; +import java.util.ArrayList; +import jnlp.converter.JNLPConverter; +import jnlp.converter.parser.exception.MissingFieldException; +import jnlp.converter.parser.exception.BadFieldException; +import jnlp.converter.parser.exception.JNLParseException; +import jnlp.converter.parser.xml.XMLEncoding; +import jnlp.converter.parser.xml.XMLParser; +import jnlp.converter.parser.xml.XMLNode; +import jnlp.converter.parser.JNLPDesc.AssociationDesc; +import jnlp.converter.parser.JNLPDesc.IconDesc; +import jnlp.converter.parser.JNLPDesc.InformationDesc; +import jnlp.converter.parser.JNLPDesc.ShortcutDesc; +import jnlp.converter.parser.ResourcesDesc.JARDesc; +import jnlp.converter.parser.ResourcesDesc.JREDesc; +import jnlp.converter.Log; +import jnlp.converter.parser.ResourcesDesc.ExtensionDesc; +import jnlp.converter.parser.ResourcesDesc.PropertyDesc; +import org.xml.sax.SAXParseException; + +public class XMLFormat { + + public static XMLNode parseBits(byte[] bits) throws JNLParseException { + return parse(decode(bits)); + } + + private static String decode(byte[] bits) throws JNLParseException { + try { + return XMLEncoding.decodeXML(bits); + } catch (Exception e) { + throw new JNLParseException(e, + "exception determining encoding of jnlp file", 0); + } + } + + private static XMLNode parse(String source) throws JNLParseException { + try { + return (new XMLParser(source).parse()); + } catch (SAXParseException spe) { + throw new JNLParseException(spe, + "exception parsing jnlp file", spe.getLineNumber()); + } catch (Exception e) { + throw new JNLParseException(e, + "exception parsing jnlp file", 0); + } + } + + /** + * thisCodebase, if set, is used to determine the codebase, + * if JNLP codebase is not absolute. + * + * @param thisCodebase base URL of this JNLPDesc location + */ + public static JNLPDesc parse(byte[] bits, URL thisCodebase, String jnlp) + throws Exception { + + JNLPDesc jnlpd = new JNLPDesc(); + String source = decode(bits).trim(); + XMLNode root = parse(source); + + if (root == null || root.getName() == null) { + throw new JNLParseException(null, null, 0); + } + + // Check that root element is a tag + if (!root.getName().equals("jnlp")) { + throw (new MissingFieldException(source, "")); + } + + // Read attributes (path is empty, i.e., "") + // (spec, version, codebase, href) + String specVersion = XMLUtils.getAttribute(root, "", "spec", "1.0+"); + jnlpd.setSpecVersion(specVersion); + String version = XMLUtils.getAttribute(root, "", "version"); + jnlpd.setVersion(version); + + // Make sure the codebase URL ends with a '/'. + // + // Regarding the JNLP spec, + // the thisCodebase is used to determine the codebase. + // codebase = new URL(thisCodebase, codebase) + URL codebase = GeneralUtil.asPathURL(XMLUtils.getAttributeURL(source, + thisCodebase, root, "", "codebase")); + if (codebase == null && thisCodebase != null) { + codebase = thisCodebase; + } + jnlpd.setCodebase(codebase.toExternalForm()); + + // Get href for JNLP file + URL href = XMLUtils.getAttributeURL(source, codebase, root, "", "href"); + jnlpd.setHref(href.toExternalForm()); + + // Read attributes + if (XMLUtils.isElementPath(root, "")) { + jnlpd.setIsSandbox(false); + } else if (XMLUtils.isElementPath(root, + "")) { + jnlpd.setIsSandbox(false); + } + + // We can be fxapp, and also be applet, or application, or neither + boolean isFXApp = false; + boolean isApplet = false; + if (XMLUtils.isElementPath(root, "")) { + // no new type for javafx-desc - needs one of the others + buildFXAppDesc(source, root, "", jnlpd); + jnlpd.setIsFXApp(true); + isFXApp = true; + } + + /* + * Note - the jnlp specification says there must be exactly one of + * the descriptor types. This code has always violated (or at least + * not checked for) that condition. + * Instead it uses precedent order app, component, installer, applet + * and ignores any other descriptors given. + */ + if (XMLUtils.isElementPath(root, "")) { + buildApplicationDesc(source, root, jnlpd); + } else if (XMLUtils.isElementPath(root, "")) { + jnlpd.setIsLibrary(true); + } else if (XMLUtils.isElementPath(root, "")) { + Log.warning(" is not supported and will be ignored in " + jnlp); + jnlpd.setIsInstaller(true); + } else if (XMLUtils.isElementPath(root, "")) { + isApplet = true; + } else { + if (!isFXApp) { + throw (new MissingFieldException(source, + "(||" + + "|)")); + } + } + + if (isApplet && !isFXApp) { + Log.error("Applet based applications deployed with element are not supported."); + } + + if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) { + buildInformationDesc(source, codebase, root, jnlpd); + } + + if (!jnlpd.isInstaller()) { + buildResourcesDesc(source, codebase, root, false, jnlpd); + } + + if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) { + jnlpd.parseResourceDesc(); + } + + if (!jnlpd.isInstaller()) { + if (jnlpd.isSandbox()) { + if (jnlpd.isLibrary()) { + Log.warning(jnlp + " is sandbox extension. JNLPConverter does not support sandbox environment and converted application will run without security manager."); + } else { + Log.warning("This is sandbox Web-Start application. JNLPConverter does not support sandbox environment and converted application will run without security manager."); + } + } + } + + return jnlpd; + } + + /** + * Create a combine informationDesc in the two informationDesc. + * The information present in id1 overwrite the information present in id2 + */ + private static InformationDesc combineInformationDesc( + InformationDesc id1, InformationDesc id2) { + if (id1 == null) { + return id2; + } + if (id2 == null) { + return id1; + } + + String t1 = id1.getTitle(); + String title = (t1 != null && t1.length() > 0) ? + t1 : id2.getTitle(); + String v1 = id1.getVendor(); + String vendor = (v1 != null && v1.length() > 0) ? + v1 : id2.getVendor(); + + /** Copy descriptions */ + String[] descriptions = new String[InformationDesc.NOF_DESC]; + for (int i = 0; i < descriptions.length; i++) { + descriptions[i] = (id1.getDescription(i) != null) + ? id1.getDescription(i) : id2.getDescription(i); + } + + /** Icons */ + ArrayList iconList = new ArrayList<>(); + if (id2.getIcons() != null) { + iconList.addAll(Arrays.asList(id2.getIcons())); + } + if (id1.getIcons() != null) { + iconList.addAll(Arrays.asList(id1.getIcons())); + } + IconDesc[] icons = new IconDesc[iconList.size()]; + icons = iconList.toArray(icons); + + ShortcutDesc hints = (id1.getShortcut() != null) ? + id1.getShortcut() : id2.getShortcut(); + + AssociationDesc[] asd = ( AssociationDesc[] ) addArrays( + (Object[])id1.getAssociations(), (Object[])id2.getAssociations()); + + return new InformationDesc(title, + vendor, + descriptions, + icons, + hints, + asd); + } + + /** Extract data from tag */ + private static void buildInformationDesc(final String source, final URL codebase, XMLNode root, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + final ArrayList list = new ArrayList<>(); + + // Iterates over all nodes ignoring the type + XMLUtils.visitElements(root, + "", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws + BadFieldException, MissingFieldException { + + // Check for right os, arch, and locale + String[] os = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "os", null)); + String[] arch = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "arch", null)); + String[] locale = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "locale", null)); + if (GeneralUtil.prefixMatchStringList( + os, GeneralUtil.getOSFullName()) && + GeneralUtil.prefixMatchArch(arch) && + matchDefaultLocale(locale)) + { + // Title, vendor + String title = XMLUtils.getElementContents(e, ""); + String vendor = XMLUtils.getElementContents(e, "<vendor>"); + + // Descriptions + String[] descriptions = + new String[InformationDesc.NOF_DESC]; + descriptions[InformationDesc.DESC_DEFAULT] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "", null); + descriptions[InformationDesc.DESC_ONELINE] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "one-line", null); + descriptions[InformationDesc.DESC_SHORT] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "short", null); + descriptions[InformationDesc.DESC_TOOLTIP] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "tooltip", null); + + // Icons + IconDesc[] icons = getIconDescs(source, codebase, e); + + // Shortcut hints + ShortcutDesc shortcuts = getShortcutDesc(e); + + // Association hints + AssociationDesc[] associations = getAssociationDesc( + source, codebase, e); + + list.add(new InformationDesc( + title, vendor, descriptions, icons, + shortcuts, associations)); + } + } + }); + + /* Combine all information desc. information in a single one for + * the current locale using the following priorities: + * 1. locale == language_country_variant + * 2. locale == lauguage_country + * 3. locale == lauguage + * 4. no or empty locale + */ + InformationDesc normId = new InformationDesc(null, null, null, null, null, null); + for (InformationDesc id : list) { + normId = combineInformationDesc(id, normId); + } + + jnlpd.setTitle(normId.getTitle()); + jnlpd.setVendor(normId.getVendor()); + jnlpd.setDescriptions(normId.getDescription()); + jnlpd.setIcons(normId.getIcons()); + jnlpd.setShortcuts(normId.getShortcut()); + jnlpd.setAssociations(normId.getAssociations()); + } + + private static Object[] addArrays (Object[] a1, Object[] a2) { + if (a1 == null) { + return a2; + } + if (a2 == null) { + return a1; + } + ArrayList<Object> list = new ArrayList<>(); + int i; + for (i=0; i<a1.length; list.add(a1[i++])); + for (i=0; i<a2.length; list.add(a2[i++])); + return list.toArray(a1); + } + + public static boolean matchDefaultLocale(String[] localeStr) { + return GeneralUtil.matchLocale(localeStr, GeneralUtil.getDefaultLocale()); + } + + /** Extract data from <resources> tag. There is only one. */ + static void buildResourcesDesc(final String source, + final URL codebase, XMLNode root, final boolean ignoreJres, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + // Extract classpath directives + final ResourcesDesc rdesc = new ResourcesDesc(); + + // Iterate over all entries + XMLUtils.visitElements(root, "<resources>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) + throws MissingFieldException, BadFieldException { + // Check for right os, archictecture, and locale + String[] os = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "os", null)); + final String arch = XMLUtils.getAttribute(e, "", "arch", null); + String[] locale = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "locale", null)); + if (GeneralUtil.prefixMatchStringList( + os, GeneralUtil.getOSFullName()) + && matchDefaultLocale(locale)) { + // Now visit all children in this node + XMLUtils.visitChildrenElements(e, + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e2) + throws MissingFieldException, BadFieldException { + handleResourceElement(source, codebase, + e2, rdesc, ignoreJres, arch, jnlpd); + } + }); + } + } + }); + + if (!rdesc.isEmpty()) { + jnlpd.setResourcesDesc(rdesc); + } + } + + private static IconDesc[] getIconDescs(final String source, + final URL codebase, XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<IconDesc> answer = new ArrayList<>(); + XMLUtils.visitElements(e, "<icon>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode icon) throws + MissingFieldException, BadFieldException { + String kindStr = XMLUtils.getAttribute(icon, "", "kind", ""); + URL href = XMLUtils.getRequiredURL(source, codebase, icon, "", "href"); + + if (href != null) { + if (!JNLPConverter.isIconSupported(href.toExternalForm())) { + return; + } + } + + int kind; + if (kindStr == null || kindStr.isEmpty() || kindStr.equals("default")) { + kind = IconDesc.ICON_KIND_DEFAULT; + } else if (kindStr.equals("shortcut")) { + kind = IconDesc.ICON_KIND_SHORTCUT; + } else { + Log.warning("Ignoring unsupported icon \"" + href + "\" with kind \"" + kindStr + "\"."); + return; + } + + answer.add(new IconDesc(href, kind)); + } + }); + return answer.toArray(new IconDesc[answer.size()]); + } + + private static ShortcutDesc getShortcutDesc(XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<ShortcutDesc> shortcuts = new ArrayList<>(); + + XMLUtils.visitElements(e, "<shortcut>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode shortcutNode) + throws MissingFieldException, BadFieldException { + boolean desktopHinted = + XMLUtils.isElementPath(shortcutNode, "<desktop>"); + boolean menuHinted = + XMLUtils.isElementPath(shortcutNode, "<menu>"); + String submenuHinted = + XMLUtils.getAttribute(shortcutNode, "<menu>", "submenu"); + shortcuts.add(new ShortcutDesc(desktopHinted, menuHinted, submenuHinted)); + } + }); + + if (shortcuts.size() > 0) { + return shortcuts.get(0); + } + return null; + } + + private static AssociationDesc[] getAssociationDesc(final String source, + final URL codebase, XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<AssociationDesc> answer = new ArrayList<>(); + XMLUtils.visitElements(e, "<association>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode node) + throws MissingFieldException, BadFieldException { + + String extensions = XMLUtils.getAttribute( + node, "", "extensions"); + + String mimeType = XMLUtils.getAttribute( + node, "", "mime-type"); + String description = XMLUtils.getElementContents( + node, "<description>"); + + URL icon = XMLUtils.getAttributeURL( + source, codebase, node, "<icon>", "href"); + + if (!JNLPConverter.isIconSupported(icon.toExternalForm())) { + icon = null; + } + + if (extensions == null && mimeType == null) { + throw new MissingFieldException(source, + "<association>(<extensions><mime-type>)"); + } else if (extensions == null) { + throw new MissingFieldException(source, + "<association><extensions>"); + } else if (mimeType == null) { + throw new MissingFieldException(source, + "<association><mime-type>"); + } + + // don't support uppercase extension and mime-type on gnome. + if ("gnome".equals(System.getProperty("sun.desktop"))) { + extensions = extensions.toLowerCase(); + mimeType = mimeType.toLowerCase(); + } + + answer.add(new AssociationDesc(extensions, mimeType, + description, icon)); + } + }); + return answer.toArray( + new AssociationDesc[answer.size()]); + } + + /** Handle the individual entries in a resource desc */ + private static void handleResourceElement(String source, URL codebase, + XMLNode e, ResourcesDesc rdesc, boolean ignoreJres, String arch, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + + String tag = e.getName(); + + boolean matchArch = GeneralUtil.prefixMatchArch( + GeneralUtil.getStringList(arch)); + + + if (matchArch && (tag.equals("jar") || tag.equals("nativelib"))) { + /* + * jar/nativelib elements + */ + URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href"); + String version = XMLUtils.getAttribute(e, "", "version", null); + + String mainStr = XMLUtils.getAttribute(e, "", "main"); + boolean isNativeLib = tag.equals("nativelib"); + + boolean isMain = "true".equalsIgnoreCase(mainStr); + + JARDesc jd = new JARDesc(href, version, isMain, isNativeLib, rdesc); + rdesc.addResource(jd); + } else if (matchArch && tag.equals("property")) { + /* + * property tag + */ + String name = XMLUtils.getRequiredAttribute(source, e, "", "name"); + String value = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + + rdesc.addResource(new PropertyDesc(name, value)); + } else if (matchArch && tag.equals("extension")) { + URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href"); + String version = XMLUtils.getAttribute(e, "", "version", null); + rdesc.addResource(new ExtensionDesc(href, version)); + } else if ((tag.equals("java") || tag.equals("j2se")) && !ignoreJres) { + /* + * j2se element + */ + String version = + XMLUtils.getRequiredAttribute(source, e, "", "version"); + String minheapstr = + XMLUtils.getAttribute(e, "", "initial-heap-size"); + String maxheapstr = + XMLUtils.getAttribute(e, "", "max-heap-size"); + + String vmargs = + XMLUtils.getAttribute(e, "", "java-vm-args"); + + if (jnlpd.isJRESet()) { + if (vmargs == null) { + vmargs = "none"; + } + Log.warning("Ignoring repeated element <" + tag + "> with version " + version + + " and java-vm-args: " + vmargs); + return; + } + + long minheap = GeneralUtil.heapValToLong(minheapstr); + long maxheap = GeneralUtil.heapValToLong(maxheapstr); + + ResourcesDesc cbs = null; + buildResourcesDesc(source, codebase, e, true, null); + + // JRE + JREDesc jreDesc = new JREDesc( + version, + minheap, + maxheap, + vmargs, + cbs, + arch); + + rdesc.addResource(jreDesc); + + jnlpd.setIsJRESet(true); + } + } + + /** Extract data from the application-desc tag */ + private static void buildApplicationDesc(final String source, + XMLNode root, JNLPDesc jnlpd) throws MissingFieldException, BadFieldException { + + String mainclass = XMLUtils.getClassName(source, root, + "<application-desc>", "main-class", false); + String appType = XMLUtils.getAttribute(root, "<application-desc>", + "type", "Java"); + String progressclass = XMLUtils.getClassName(source, root, + "<application-desc>", "progress-class", false); + if (progressclass != null && !progressclass.isEmpty()) { + Log.warning("JNLPConverter does not support progress indication. \"" + progressclass + "\" will not be loaded and will be ignored."); + } + + if (!("Java".equalsIgnoreCase(appType) || + "JavaFx".equalsIgnoreCase(appType))) { + throw new BadFieldException(source, XMLUtils.getPathString(root) + + "<application-desc>type", appType); + } + + if ("JavaFx".equalsIgnoreCase(appType)) { + jnlpd.setIsFXApp(true); + } + + XMLUtils.visitElements(root, "<application-desc><argument>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException { + String arg = XMLUtils.getElementContents(e, "", null); + if (arg == null) { + throw new BadFieldException(source, XMLUtils.getPathString(e), ""); + } + jnlpd.addArguments(arg); + } + }); + + XMLUtils.visitElements(root, "<application-desc><param>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, + BadFieldException { + String pn = XMLUtils.getRequiredAttribute( + source, e, "", "name"); + String pv = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + jnlpd.setProperty(pn, pv); + } + }); + jnlpd.setMainClass(mainclass, false); + } + + /** Extract data from the javafx-desc tag */ + private static void buildFXAppDesc(final String source, + XMLNode root, String element, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + String mainclass = XMLUtils.getClassName(source, root, element, + "main-class", true); + String name = XMLUtils.getRequiredAttribute(source, root, + "<javafx-desc>", "name"); + + /* extract arguments */ + XMLUtils.visitElements(root, "<javafx-desc><argument>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException { + String arg = XMLUtils.getElementContents(e, "", null); + if (arg == null) { + throw new BadFieldException(source, XMLUtils.getPathString(e), ""); + } + jnlpd.addArguments(arg); + } + }); + + /* extract parameters */ + XMLUtils.visitElements(root, "<javafx-desc><param>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, + BadFieldException { + String pn = XMLUtils.getRequiredAttribute( + source, e, "", "name"); + String pv = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + jnlpd.setProperty(pn, pv); + } + }); + + jnlpd.setMainClass(mainclass, true); + jnlpd.setName(name); + } +}