1 /*
   2  * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jnlp.converter.parser;
  25 
  26 import java.net.URL;
  27 import java.net.MalformedURLException;
  28 
  29 import jnlp.converter.parser.exception.BadFieldException;
  30 import jnlp.converter.parser.exception.MissingFieldException;
  31 import jnlp.converter.parser.xml.XMLNode;
  32 
  33 /** Contains handy methods for looking up information
  34  *  stored in XMLNodes.
  35  */
  36 public class XMLUtils {
  37 
  38     /** Returns the value of an integer attribute */
  39     public static int getIntAttribute(String source, XMLNode root, String path, String name, int defaultvalue)
  40             throws BadFieldException {
  41         String value = getAttribute(root, path, name);
  42         if (value == null) {
  43             return defaultvalue;
  44         }
  45 
  46         try {
  47             return Integer.parseInt(value);
  48         } catch (NumberFormatException nfe) {
  49             throw new BadFieldException(source, getPathString(root) + path + name, value);
  50         }
  51     }
  52 
  53     /** Returns the value of a given attribute, or null if not set */
  54     public static String getAttribute(XMLNode root, String path, String name)
  55                                                       throws BadFieldException {
  56         return getAttribute(root, path, name, null);
  57     }
  58 
  59     /** Returns the value of a given attribute */
  60     public static String getRequiredAttributeEmptyOK(String source,
  61         XMLNode root, String path, String name) throws MissingFieldException {
  62         String value = null;
  63         XMLNode elem = findElementPath(root, path);
  64         if (elem != null) {
  65             value = elem.getAttribute(name);
  66         }
  67         if (value == null) {
  68             throw new MissingFieldException(source,
  69                                             getPathString(root)+ path + name);
  70         }
  71         return value;
  72     }
  73 
  74     /*** Returns the value of an attribute, which must be a valid class name */
  75     public static String getClassName(String source, XMLNode root,
  76         String path, String name, boolean required)
  77             throws BadFieldException, MissingFieldException {
  78 
  79         String className;
  80         if (required) {
  81             className = getRequiredAttribute(source, root, path, name);
  82         } else {
  83             className = getAttribute(root, path, name);
  84         }
  85         if (className != null && className.endsWith(".class")) {
  86             int i = className.lastIndexOf(".class");
  87             String cname = className.substring(0, i);
  88             return cname;
  89         }
  90         return className;
  91     }
  92 
  93     /** Returns the value of a given attribute, or null if not set */
  94     public static String getRequiredAttribute(String source, XMLNode root,
  95             String path, String name) throws MissingFieldException, BadFieldException {
  96         String s = getAttribute(root, path, name, null);
  97         if (s == null) {
  98             throw new MissingFieldException(source, getPathString(root) + path + name);
  99         }
 100         s = s.trim();
 101         return (s.length() == 0) ? null : s;
 102     }
 103 
 104     /** Returns the value of a given attribute, or the default value 'def' if not set */
 105     public static String getAttribute(XMLNode root, String path, String name,
 106             String def) throws BadFieldException {
 107         XMLNode elem = findElementPath(root, path);
 108         if (elem == null) {
 109             return def;
 110         }
 111         String value = elem.getAttribute(name);
 112         return (value == null || value.length() == 0) ? def : value;
 113     }
 114 
 115     /** Expands a URL into an absolute URL from a relative URL */
 116     public static URL getAttributeURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException {
 117         String value = getAttribute(root, path, name);
 118         if (value == null) return null;
 119         try {
 120             if (value.startsWith("jar:")) {
 121                 int bang = value.indexOf("!/");
 122                 if (bang > 0) {
 123                     String entry = value.substring(bang);
 124                     String urlString = value.substring(4, bang);
 125                     URL url = (base == null) ?
 126                         new URL(urlString) : new URL(base, urlString);
 127                     return new URL("jar:" + url.toString() + entry);
 128                 }
 129             }
 130             return (base == null) ? new URL(value) : new URL(base, value);
 131         } catch(MalformedURLException mue) {
 132             if (mue.getMessage().contains("https")) {
 133                 throw new BadFieldException(source, "<jnlp>", "https");
 134             }
 135             throw new BadFieldException(source, getPathString(root) + path + name, value);
 136         }
 137     }
 138 
 139     /** Returns the value of an attribute as a URL or null if not set */
 140     public static URL getAttributeURL(String source, XMLNode root, String path, String name) throws BadFieldException {
 141         return getAttributeURL(source, null, root, path, name);
 142     }
 143 
 144     public static URL getRequiredURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
 145         URL url = getAttributeURL(source, base, root, path, name);
 146         if (url == null) {
 147             throw new MissingFieldException(source, getPathString(root) + path + name);
 148         }
 149         return url;
 150     }
 151 
 152     /** Returns the value of an attribute as a URL. Throws a MissingFieldException if the
 153      *  attribute is not defined
 154      */
 155     public static URL getRequiredURL(String source, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
 156         return getRequiredURL(source, null, root, path, name);
 157     }
 158 
 159     /** Returns true if the path exists in the document, otherwise false */
 160     public static boolean isElementPath(XMLNode root, String path) {
 161         return findElementPath(root, path) != null;
 162     }
 163 
 164     public static URL getElementURL(String source, XMLNode root, String path) throws BadFieldException {
 165         String value = getElementContents(root, path);
 166         try {
 167             return new URL(value);
 168         } catch(MalformedURLException mue) {
 169             throw new BadFieldException(source, getPathString(root) + path, value);
 170         }
 171     }
 172 
 173     /** Returns a string describing the current location in the DOM */
 174     public static String getPathString(XMLNode e) {
 175         return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">";
 176     }
 177 
 178     /** Returns the contents of an element with the given path and an attribute matching a specific value. Returns
 179      *  NULL if not found
 180      */
 181     public static String getElementContentsWithAttribute(XMLNode root, String path, String attr, String val, String defaultvalue)
 182             throws BadFieldException, MissingFieldException {
 183         XMLNode e = getElementWithAttribute(root, path, attr, val);
 184         if (e == null) {
 185             return defaultvalue;
 186         }
 187         return getElementContents(e, "", defaultvalue);
 188     }
 189 
 190     public static URL getAttributeURLWithAttribute(String source, XMLNode root, String path, String attrcond, String val,
 191             String name, URL defaultvalue)
 192             throws BadFieldException, MissingFieldException {
 193         XMLNode e = getElementWithAttribute(root, path, attrcond, val);
 194         if (e == null) {
 195             return defaultvalue;
 196         }
 197         URL url = getAttributeURL(source, e, "", name);
 198         if (url == null) {
 199             return defaultvalue;
 200         }
 201         return url;
 202     }
 203 
 204     /** Returns an element with the given path and an attribute matching a specific value. Returns
 205      *  NULL if not found
 206      */
 207     public static XMLNode getElementWithAttribute(XMLNode root, String path, final String attr, final String val)
 208             throws BadFieldException, MissingFieldException {
 209         final XMLNode[] result = {null};
 210         visitElements(root, path, new ElementVisitor() {
 211             public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException {
 212                 if (result[0] == null && e.getAttribute(attr).equals(val)) {
 213                     result[0] = e;
 214                 }
 215             }
 216         });
 217         return result[0];
 218     }
 219 
 220     /** Like getElementContents(...) but with a defaultValue of null */
 221     public static String getElementContents(XMLNode root, String path) {
 222         return getElementContents(root, path, null);
 223     }
 224 
 225     /** Returns the value of the last element tag in the path, e.g.,  <..><tag>value</tag>. The DOM is assumes
 226      *  to be normalized. If no value is found, the defaultvalue is returned
 227      */
 228     public static String getElementContents(XMLNode root, String path, String defaultvalue) {
 229         XMLNode e = findElementPath(root, path);
 230         if (e == null) {
 231             return defaultvalue;
 232         }
 233         XMLNode n = e.getNested();
 234         if (n != null && !n.isElement()) {
 235             return n.getName();
 236         }
 237         return defaultvalue;
 238     }
 239 
 240     /** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element
 241      *  node for that tag, or null if it does not exist. If multiple elements exists with same
 242      *  path the first is returned
 243      */
 244     public static XMLNode findElementPath(XMLNode elem, String path) {
 245         // End condition. Root null -> path does not exist
 246         if (elem == null) {
 247             return null;
 248         }
 249 
 250         // End condition. String empty, return current root
 251         if (path == null || path.length() == 0) {
 252             return elem;
 253         }
 254 
 255         // Strip of first tag
 256         int idx = path.indexOf('>');
 257         if (!(path.charAt(0) == '<')) {
 258             throw new IllegalArgumentException("bad path. Missing begin tag");
 259         }
 260         if (idx == -1) {
 261             throw new IllegalArgumentException("bad path. Missing end tag");
 262         }
 263         String head = path.substring(1, idx);
 264         String tail = path.substring(idx + 1);
 265         return findElementPath(findChildElement(elem, head), tail);
 266     }
 267 
 268     /** Returns an child element with the current tag name or null. */
 269     public static XMLNode findChildElement(XMLNode elem, String tag) {
 270         XMLNode n = elem.getNested();
 271         while (n != null) {
 272             if (n.isElement() && n.getName().equals(tag)) {
 273                 return n;
 274             }
 275             n = n.getNext();
 276         }
 277         return null;
 278     }
 279 
 280     /** Iterator class */
 281     public abstract static class ElementVisitor {
 282         abstract public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException;
 283     }
 284 
 285     /** Visits all elements which matches the <path>. The iteration is only
 286      *  done on the last element in the path.
 287      */
 288     public static void visitElements(XMLNode root, String path, ElementVisitor ev)
 289             throws BadFieldException, MissingFieldException {
 290         // Get last element in path
 291         int idx = path.lastIndexOf('<');
 292         if (idx == -1) {
 293             throw new IllegalArgumentException(
 294                     "bad path. Must contain atleast one tag");
 295         }
 296         if (path.length() == 0 || path.charAt(path.length() - 1) != '>') {
 297             throw new IllegalArgumentException("bad path. Must end with a >");
 298         }
 299         String head = path.substring(0, idx);
 300         String tag = path.substring(idx + 1, path.length() - 1);
 301 
 302         XMLNode elem = findElementPath(root, head);
 303         if (elem == null) {
 304             return;
 305         }
 306 
 307         // Iterate through all child nodes
 308         XMLNode n = elem.getNested();
 309         while (n != null) {
 310             if (n.isElement() && n.getName().equals(tag)) {
 311                 ev.visitElement(n);
 312             }
 313             n = n.getNext();
 314         }
 315     }
 316 
 317     public static void visitChildrenElements(XMLNode elem, ElementVisitor ev)
 318             throws BadFieldException, MissingFieldException {
 319         // Iterate through all child nodes
 320         XMLNode n = elem.getNested();
 321         while (n != null) {
 322             if (n.isElement()) {
 323                 ev.visitElement(n);
 324             }
 325             n = n.getNext();
 326         }
 327     }
 328 }