1 /*
   2  * Copyright (c) 2006, 2019, 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.util.Properties;
  28 import java.util.ArrayList;
  29 import java.util.Arrays;
  30 import java.util.Enumeration;
  31 import java.util.LinkedList;
  32 import java.util.List;
  33 import java.util.concurrent.CopyOnWriteArrayList;
  34 import jnlp.converter.HTTPHelper;
  35 
  36 /**
  37  * This class contains information about the codebase and properties, i.e., how
  38  * to locate the classes and optional-packages
  39  */
  40 public class ResourcesDesc implements ResourceType {
  41 
  42     private final List<ResourceType> _list;
  43     private volatile JNLPDesc _parent = null;
  44 
  45     /**
  46      * Create empty resource list
  47      */
  48     public ResourcesDesc() {
  49         _list = new CopyOnWriteArrayList<>();
  50     }
  51 
  52     public JNLPDesc getParent() {
  53         return _parent;
  54     }
  55 
  56     void setParent(JNLPDesc parent) {
  57         _parent = parent;
  58         for (int i = 0; i < _list.size(); i++) {
  59             Object o = _list.get(i);
  60             if (o instanceof JREDesc) {
  61                 JREDesc jredesc = (JREDesc) o;
  62                 if (jredesc.getNestedResources() != null) {
  63                     jredesc.getNestedResources().setParent(parent);
  64                 }
  65             }
  66         }
  67     }
  68 
  69     public void addResource(ResourceType rd) {
  70         if (rd != null) {
  71             _list.add(rd);
  72         }
  73     }
  74 
  75     boolean isEmpty() {
  76         return _list.isEmpty();
  77     }
  78 
  79     public JARDesc[] getLocalJarDescs() {
  80         ArrayList<JARDesc> jds = new ArrayList<>(_list.size());
  81         for (ResourceType rt : _list) {
  82             if (rt instanceof JARDesc) {
  83                 jds.add((JARDesc) rt);
  84             }
  85         }
  86         return jds.toArray(new JARDesc[jds.size()]);
  87     }
  88 
  89     public JREDesc getJreDesc() {
  90         for (ResourceType rt : _list) {
  91             if (rt instanceof JREDesc) {
  92                 return (JREDesc)rt;
  93             }
  94         }
  95 
  96         return null;
  97     }
  98 
  99     public ExtensionDesc[] getExtensionDescs() throws Exception {
 100         final ArrayList<ExtensionDesc> extList = new ArrayList<>();
 101         visit(new ResourceVisitor() {
 102             @Override
 103             public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
 104               // add all extensiondesc recursively
 105                 addExtToList(extList);
 106             }
 107         });
 108         return extList.toArray(new ExtensionDesc[extList.size()]);
 109     }
 110 
 111     public JARDesc[] getAllJarDescs() throws Exception {
 112         List<JARDesc> jarList = new ArrayList<>();
 113         addJarsToList(jarList);
 114         return jarList.toArray(new JARDesc[jarList.size()]);
 115     }
 116 
 117     /**
 118      * Add to a list of all the ExtensionDesc. This method goes recusivly through
 119      * all ExtensionDesc
 120      */
 121     private void addExtToList(final List<ExtensionDesc> list) throws Exception {
 122         // Iterate through list an add ext jnlp to the list.
 123         visit(new ResourceVisitor() {
 124             @Override
 125             public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
 126                 if (ed.getExtensionDesc() != null) {
 127                     ed.getExtensionDesc().getMainJar();
 128                     ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
 129                     if (rd != null) {
 130                         rd.addExtToList(list);
 131                     }
 132                 }
 133                 list.add(ed);
 134             }
 135         });
 136     }
 137 
 138     private void addJarsToList(final List<JARDesc> list) throws Exception {
 139 
 140         // Iterate through list an add resources to the list.
 141         // The ordering of resources are preserved
 142         visit(new ResourceVisitor() {
 143             @Override
 144             public void visitJARDesc(JARDesc jd) {
 145                 list.add(jd);
 146             }
 147 
 148             @Override
 149             public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
 150                 if (ed.getExtensionDesc() != null) {
 151                     ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
 152                     if (rd != null) {
 153                         rd.addJarsToList(list);
 154                     }
 155                 }
 156             }
 157         });
 158     }
 159 
 160     /**
 161      * Get all the resources needed when a specific resource is requested.
 162      * Returns null if no resource was found
 163      */
 164     public JARDesc[] getResource(final URL location) throws Exception {
 165         final JARDesc[] resources = new JARDesc[1];
 166         // Find the given resource
 167         visit(new ResourceVisitor() {
 168             @Override
 169             public void visitJARDesc(JARDesc jd) {
 170                 if (GeneralUtil.sameURLs(jd.getLocation(), location)) {
 171                     resources[0] = jd;
 172                 }
 173             }
 174         });
 175 
 176         // Found no resource?
 177         if (resources[0] == null) {
 178             return null;
 179         }
 180 
 181         // No part, so just one resource
 182         return resources;
 183     }
 184 
 185     /* Returns the Expected Main Jar
 186      *    first jar with attribute main="true"
 187      *    else first jar if none has that attribute
 188      *    will look in extensions, and nested resource blocks if matching
 189      */
 190     protected JARDesc getMainJar() throws Exception {
 191         // Normal trick to get around final arguments to inner classes
 192         final JARDesc[] results = new JARDesc[2];
 193 
 194         visit(new ResourceVisitor() {
 195             @Override
 196             public void visitJARDesc(JARDesc jd) {
 197                 if (jd.isJavaFile()) {
 198                     // Keep track of first Java File
 199                     if (results[0] == null || results[0].isNativeLib()) {
 200                         results[0] = jd;
 201                     }
 202                     // Keep tack of Java File marked main
 203                     if (jd.isMainJarFile()) {
 204                         results[1] = jd;
 205                     }
 206                 } else if (jd.isNativeLib()) {
 207                     // if jnlp extension has only native lib
 208                     if (results[0] == null) {
 209                         results[0] = jd;
 210                     }
 211                 }
 212             }
 213 
 214             @Override
 215             public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
 216             // only check if no main yet and it is not an installer
 217                 if (results[1] == null && !ed.isInstaller()) {
 218                     JNLPDesc extLd = ed.getExtensionDesc();
 219                     if (extLd != null && extLd.isLibrary()) {
 220                         ResourcesDesc rd = extLd.getResourcesDesc();
 221                         if (rd != null) {
 222                           // look for main jar in extension resource
 223                             rd.visit(this);
 224                         }
 225                     }
 226                 }
 227             }
 228         });
 229 
 230         // Default is the first, if none is specified as main. This might
 231         // return NULL if there is no JAR resources.
 232         JARDesc first = results[0];
 233         JARDesc main = results[1];
 234 
 235         // if main is null then return first;
 236         // libraries have no such thing as a main jar, so return first;
 237         // otherwise return main
 238         // only returns null if there are no jars.
 239         return (main == null) ? first : main;
 240     }
 241 
 242     /*
 243      *  Get the properties defined for this object
 244      */
 245     public Properties getResourceProperties() throws Exception {
 246         final Properties props = new Properties();
 247         visit(new ResourceVisitor() {
 248             @Override
 249             public void visitPropertyDesc(PropertyDesc pd) {
 250                 props.setProperty(pd.getKey(), pd.getValue());
 251             }
 252 
 253             @Override
 254             public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
 255                 JNLPDesc jnlpd = ed.getExtensionDesc();
 256                 ResourcesDesc rd = jnlpd.getResourcesDesc();
 257                 if (rd != null) {
 258                     Properties extProps = rd.getResourceProperties();
 259                     Enumeration e = extProps.propertyNames();
 260                     while (e.hasMoreElements()) {
 261                         String key = (String) e.nextElement();
 262                         String value = extProps.getProperty(key);
 263                         props.setProperty(key, value);
 264                     }
 265                 }
 266             }
 267         });
 268         return props;
 269     }
 270 
 271     /*
 272      *  Get the properties defined for this object, in the right order.
 273      */
 274     public List<Property> getResourcePropertyList() throws Exception {
 275         final LinkedList<Property> propList = new LinkedList<>();
 276         visit(new ResourceVisitor() {
 277             @Override
 278             public void visitPropertyDesc(PropertyDesc pd) {
 279                 propList.add(new Property(pd.getKey(), pd.getValue()));
 280             }
 281         });
 282         return propList;
 283     }
 284 
 285     /**
 286      * visitor dispatch
 287      */
 288     @Override
 289     public void visit(ResourceVisitor rv) throws Exception {
 290         for (int i = 0; i < _list.size(); i++) {
 291             ResourceType rt = _list.get(i);
 292             rt.visit(rv);
 293         }
 294     }
 295 
 296     public void addNested(ResourcesDesc nested) throws Exception {
 297         if (nested != null) {
 298             nested.visit(new ResourceVisitor() {
 299                 @Override
 300                 public void visitJARDesc(JARDesc jd) {
 301                     _list.add(jd);
 302                 }
 303 
 304                 @Override
 305                 public void visitPropertyDesc(PropertyDesc pd) {
 306                     _list.add(pd);
 307                 }
 308 
 309                 @Override
 310                 public void visitExtensionDesc(ExtensionDesc ed) {
 311                     _list.add(ed);
 312                 }
 313             });
 314         }
 315 
 316     }
 317 
 318     public static class JARDesc implements ResourceType {
 319 
 320         private URL _location;
 321         private String _locationString;
 322         private String _version;
 323         private boolean _isNativeLib;
 324         private boolean _isMainFile;  // Only used for Java JAR files (a main JAR file is implicitly eager)
 325         private ResourcesDesc _parent;   // Back-pointer to the Resources that contains this JAR
 326 
 327         public JARDesc(URL location, String version, boolean isMainFile, boolean isNativeLib, ResourcesDesc parent) {
 328             _location = location;
 329             _locationString = GeneralUtil.toNormalizedString(location);
 330             _version = version;
 331             _isMainFile = isMainFile;
 332             _isNativeLib = isNativeLib;
 333             _parent = parent;
 334         }
 335 
 336         /**
 337          * Type of JAR resource
 338          */
 339         public boolean isNativeLib() {
 340             return _isNativeLib;
 341         }
 342 
 343         public boolean isJavaFile() {
 344             return !_isNativeLib;
 345         }
 346 
 347         /**
 348          * Returns URL/version for JAR file
 349          */
 350         public URL getVersionLocation() throws Exception {
 351             if (getVersion() == null) {
 352                 return _location;
 353             } else {
 354                 return GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion());
 355             }
 356         }
 357 
 358         public URL getLocation() {
 359             return _location;
 360         }
 361 
 362         public String getVersion() {
 363             return _version;
 364         }
 365 
 366         public String getName() {
 367             // File can be separated by '/' or '\\'
 368             int index;
 369             int index1 = _locationString.lastIndexOf('/');
 370             int index2 = _locationString.lastIndexOf('\\');
 371 
 372             if (index1 >= index2) {
 373                 index = index1;
 374             } else {
 375                 index = index2;
 376             }
 377 
 378             if (index != -1) {
 379                 return _locationString.substring(index + 1, _locationString.length());
 380             }
 381 
 382             return null;
 383         }
 384 
 385         /**
 386          * Returns if this is the main JAR file
 387          */
 388         public boolean isMainJarFile() {
 389             return _isMainFile;
 390         }
 391 
 392         /**
 393          * Get parent LaunchDesc
 394          */
 395         public ResourcesDesc getParent() {
 396             return _parent;
 397         }
 398 
 399         /**
 400          * Visitor dispatch
 401          */
 402         public void visit(ResourceVisitor rv) {
 403             rv.visitJARDesc(this);
 404         }
 405     }
 406 
 407     public static class PropertyDesc implements ResourceType {
 408 
 409         private String _key;
 410         private String _value;
 411 
 412         public PropertyDesc(String key, String value) {
 413             _key = key;
 414             _value = value;
 415         }
 416 
 417         // Accessors
 418         public String getKey() {
 419             return _key;
 420         }
 421 
 422         public String getValue() {
 423             return _value;
 424         }
 425 
 426         /**
 427          * Visitor dispatch
 428          */
 429         public void visit(ResourceVisitor rv) {
 430             rv.visitPropertyDesc(this);
 431         }
 432 
 433     }
 434 
 435     public static class JREDesc implements ResourceType {
 436 
 437         private String _version;
 438         private long _maxHeap;
 439         private long _minHeap;
 440         private String _vmargs;
 441         private ResourcesDesc _resourceDesc;
 442         private JNLPDesc _extensioDesc;
 443         private String _archList;
 444 
 445         /*
 446          * Constructor to create new instance based on the requirements from JNLP file.
 447          */
 448         public JREDesc(String version, long minHeap, long maxHeap, String vmargs,
 449                        ResourcesDesc resourcesDesc, String archList) {
 450 
 451             _version = version;
 452             _maxHeap = maxHeap;
 453             _minHeap = minHeap;
 454             _vmargs = vmargs;
 455             _resourceDesc = resourcesDesc;
 456             _extensioDesc = null;
 457             _archList = archList;
 458         }
 459 
 460         public String[] getArchList() {
 461             return GeneralUtil.getStringList(_archList);
 462         }
 463 
 464         public String getVersion() {
 465             return _version;
 466         }
 467 
 468         public long getMinHeap() {
 469             return _minHeap;
 470         }
 471 
 472         public long getMaxHeap() {
 473             return _maxHeap;
 474         }
 475 
 476         public String getVmArgs() {
 477             return _vmargs;
 478         }
 479 
 480         public String[] getVmArgsList() {
 481             return GeneralUtil.getStringList(_vmargs);
 482         }
 483 
 484         public ResourcesDesc getNestedResources() {
 485             return _resourceDesc;
 486         }
 487 
 488         public JNLPDesc getExtensionDesc() {
 489             return _extensioDesc;
 490         }
 491 
 492         public void setExtensionDesc(JNLPDesc ld) {
 493             _extensioDesc = ld;
 494         }
 495 
 496         /* visitor dispatch */
 497         public void visit(ResourceVisitor rv) {
 498             rv.visitJREDesc(this);
 499         }
 500     }
 501 
 502     public static class Property implements Cloneable {
 503 
 504         public static final String JNLP_VERSION_ENABLED = "jnlp.versionEnabled";
 505 
 506         String key;
 507         String value;
 508 
 509         public Property(String spec) {
 510             spec = spec.trim();
 511             if (!spec.startsWith("-D") || spec.length() < 3) {
 512                 throw new IllegalArgumentException("Property invalid");
 513             }
 514 
 515             int endKey = spec.indexOf("=");
 516             if (endKey < 0) {
 517                 // it's legal to have no assignment
 518                 this.key = spec.substring(2); // skip "-D"
 519                 this.value = "";
 520             } else {
 521                 this.key = spec.substring(2, endKey);
 522                 this.value = spec.substring(endKey + 1);
 523             }
 524         }
 525 
 526         public static Property createProperty(String spec) {
 527             Property prop = null;
 528             try {
 529                 prop = new Property(spec);
 530             } catch (IllegalArgumentException iae) {
 531             }
 532             return prop;
 533         }
 534 
 535         public Property(String key, String value) {
 536             this.key = key;
 537             if (value != null) {
 538                 this.value = value;
 539             } else {
 540                 this.value = "";
 541             }
 542         }
 543 
 544         public String getKey() {
 545             return key;
 546         }
 547 
 548         public String getValue() {
 549             return value;
 550         }
 551 
 552         // @return String representation, unquoted, unified presentation
 553         public String toString() {
 554             if (value.length() == 0) {
 555                 return "-D" + key;
 556             }
 557             return "-D" + key + "=" + value;
 558         }
 559 
 560         public void addTo(Properties props) {
 561             props.setProperty(key, value);
 562         }
 563 
 564         // Hash Object
 565         public boolean equals(Object o) {
 566             if (!(o instanceof Property)) {
 567                 return false;
 568             }
 569             Property op = (Property) o;
 570             int hashTheirs = op.hashCode();
 571             int hashThis = hashCode();
 572             return hashTheirs == hashThis;
 573         }
 574 
 575         public int hashCode() {
 576             return key.hashCode();
 577         }
 578 
 579         private static List<Object> jnlpProps = Arrays.asList(new Object[]{
 580             JNLP_VERSION_ENABLED
 581         });
 582 
 583         public static boolean isJnlpProperty(String spec) {
 584             try {
 585                 Property p = new Property(spec);
 586                 return isJnlpPropertyKey(p.getKey());
 587             } catch (Exception e) {
 588                 return false;
 589             }
 590         }
 591 
 592         public static boolean isJnlpPropertyKey(String key) {
 593             return key != null && jnlpProps.contains(key);
 594         }
 595     }
 596 
 597     public static class ExtensionDesc implements ResourceType {
 598         // Tag elements
 599 
 600         private final URL _location;
 601         private final String _locationString;
 602         private final String _version;
 603         private final URL _codebase;
 604 
 605         // Link to launchDesc
 606         private JNLPDesc _extensionLd; // Link to launchDesc for extension
 607 
 608         public ExtensionDesc(URL location, String version) {
 609             _location = location;
 610             _locationString = GeneralUtil.toNormalizedString(location);
 611             _version = version;
 612             _codebase = GeneralUtil.asPathURL(GeneralUtil.getBase(location));
 613             _extensionLd = null;
 614         }
 615 
 616         public boolean isInstaller() throws Exception {
 617             if (getExtensionDesc() != null) {
 618                 return _extensionLd.isInstaller();
 619             }
 620             return false;
 621         }
 622 
 623         public URL getLocation() {
 624             return _location;
 625         }
 626 
 627         public String getVersionLocation() throws Exception {
 628             if (getVersion() == null) {
 629                 return _locationString;
 630             } else {
 631                 return GeneralUtil.toNormalizedString(GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion()));
 632             }
 633         }
 634 
 635         public String getVersion() {
 636             return _version;
 637         }
 638 
 639         public URL getCodebase() {
 640             return _codebase;
 641         }
 642 
 643         /*
 644          * Information about the resources
 645          */
 646         public JNLPDesc getExtensionDesc() throws Exception {
 647             if (_extensionLd == null) {
 648                 byte[] bits = HTTPHelper.getJNLPBits(getVersionLocation(), _locationString);
 649                 _extensionLd = XMLFormat.parse(bits, getCodebase(), getVersionLocation());
 650             }
 651             return _extensionLd;
 652         }
 653 
 654         public void setExtensionDesc(JNLPDesc desc) {
 655             _extensionLd = desc;
 656         }
 657 
 658         /**
 659          * Visitor dispatch
 660          */
 661         public void visit(ResourceVisitor rv) throws Exception {
 662             rv.visitExtensionDesc(this);
 663         }
 664     }
 665 }