// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
//
// 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 static net.sourceforge.jnlp.runtime.Translator.R;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.jar.Attributes;
import net.sourceforge.jnlp.cache.ResourceTracker;
import net.sourceforge.jnlp.cache.UpdatePolicy;
import net.sourceforge.jnlp.runtime.JNLPClassLoader;
import net.sourceforge.jnlp.runtime.JNLPRuntime;
import net.sourceforge.jnlp.util.logging.OutputController;
/**
* Provides methods to access the information in a Java Network
* Launching Protocol (JNLP) file. The Java Network Launching
* Protocol specifies in an XML file the information needed to
* load, cache, and run Java code over the network and in a secure
* environment.
*
* This class represents the overall information about a JNLP file
* from the jnlp element. Other information is accessed through
* objects that represent the elements of a JNLP file
* (information, resources, application-desc, etc). References to
* these objects are obtained by calling the getInformation,
* getResources, getSecurity, etc methods.
*
* @author Jon A. Maxwell (JAM) - initial author
* @version $Revision: 1.21 $
*/
public class JNLPFile {
public static final String APP_NAME = "Application-Name";
public static final String CALLER_ALLOWABLE = "Caller-Allowable-Codebase";
public static final String APP_LIBRARY_ALLOWABLE = "Application-Library-Allowable-Codebase";
// todo: save the update policy, then if file was not updated
// then do not check resources for being updated.
//
// todo: make getLaunchInfo return a superclass that all the
// launch types implement (can get codebase from it).
//
// todo: currently does not filter resources by jvm version.
//
/** the location this JNLP file was created from */
protected URL sourceLocation = null;
/** the network location of this JNLP file */
protected URL fileLocation;
/** the ParserSettings which were used to parse this file */
protected ParserSettings parserSettings = null;
/** A key that uniquely identifies connected instances (main jnlp+ext) */
protected String uniqueKey = null;
/** the URL used to resolve relative URLs in the file */
protected URL codeBase;
/** file version */
protected Version fileVersion;
/** spec version */
protected Version specVersion;
/** information */
protected List info;
protected UpdateDesc update;
/** resources */
protected List resources;
/** additional resources not in JNLP file (from command line) */
protected ResourcesDesc sharedResources = new ResourcesDesc(this, null, null, null);
/** the application description */
protected LaunchDesc launchType;
/** the component description */
protected ComponentDesc component;
/** the security descriptor */
protected SecurityDesc security;
/** the default JVM locale */
protected Locale defaultLocale = null;
/** the default OS */
protected String defaultOS = null;
/** the default arch */
protected String defaultArch = null;
/** A signed JNLP file is missing from the main jar */
private boolean missingSignedJNLP = false;
/** JNLP file contains special properties */
private boolean containsSpecialProperties = false;
/**
* List of acceptable properties (not-special)
*/
private String[] generalProperties = SecurityDesc.getJnlpRIAPermissions();
/** important manifests' attributes */
private final ManifestsAttributes manifestsAttributes = new ManifestsAttributes();
public static final String TITLE_NOT_FOUND = "Application title was not found in manifest. Check with application vendor";
{ // initialize defaults if security allows
try {
defaultLocale = Locale.getDefault();
defaultOS = System.getProperty("os.name");
defaultArch = System.getProperty("os.arch");
} catch (SecurityException ex) {
// null values will still work, and app can set defaults later
}
}
static enum Match { LANG_COUNTRY_VARIANT, LANG_COUNTRY, LANG, GENERALIZED }
/**
* Empty stub, allowing child classes to override the constructor
*/
protected JNLPFile() {
}
/**
* Create a JNLPFile from a URL.
*
* @param location the location of the JNLP file
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(URL location) throws IOException, ParseException {
this(location, new ParserSettings());
}
/**
* Create a JNLPFile from a URL checking for updates using the
* default policy.
*
* @param location the location of the JNLP file
* @param settings the parser settings to use while parsing the file
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(URL location, ParserSettings settings) throws IOException, ParseException {
this(location, (Version) null, settings);
}
/**
* Create a JNLPFile from a URL and a Version checking for updates using
* the default policy.
*
* @param location the location of the JNLP file
* @param version the version of the JNLP file
* @param settings the parser settings to use while parsing the file
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(URL location, Version version, ParserSettings settings) throws IOException, ParseException {
this(location, version, settings, JNLPRuntime.getDefaultUpdatePolicy());
}
/**
* Create a JNLPFile from a URL and a version, checking for updates
* using the specified policy.
*
* @param location the location of the JNLP file
* @param version the version of the JNLP file
* @param strict whether to enforce the spec when
* @param policy the update policy
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(URL location, Version version, ParserSettings settings, UpdatePolicy policy) throws IOException, ParseException {
this(location, version, settings, policy, null);
}
/**
* Create a JNLPFile from a URL and a version, checking for updates
* using the specified policy.
*
* @param location the location of the JNLP file
* @param version the version of the JNLP file
* @param settings the parser settings to use while parsing the file
* @param policy the update policy
* @param forceCodebase codebase to use if not specified in JNLP file.
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
protected JNLPFile(URL location, Version version, ParserSettings settings, UpdatePolicy policy, URL forceCodebase) throws IOException, ParseException {
InputStream input = openURL(location, version, policy);
this.parserSettings = settings;
parse(input, location, forceCodebase);
//Downloads the original jnlp file into the cache if possible
//(i.e. If the jnlp file being launched exist locally, but it
//originated from a website, then download the one from the website
//into the cache).
if (sourceLocation != null && "file".equals(location.getProtocol())) {
openURL(sourceLocation, version, policy);
}
this.fileLocation = location;
this.uniqueKey = Calendar.getInstance().getTimeInMillis() + "-" +
((int)(Math.random()*Integer.MAX_VALUE)) + "-" +
location;
OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "UNIQUEKEY=" + this.uniqueKey);
}
/**
* Create a JNLPFile from a URL, parent URLm a version and checking for
* updates using the specified policy.
*
* @param location the location of the JNLP file
* @param uniqueKey A string that uniquely identifies connected instances
* @param version the version of the JNLP file
* @param settings the parser settings to use while parsing the file
* @param policy the update policy
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(URL location, String uniqueKey, Version version, ParserSettings settings, UpdatePolicy policy) throws IOException, ParseException {
this(location, version, settings, policy);
this.uniqueKey = uniqueKey;
OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "UNIQUEKEY (override) =" + this.uniqueKey);
}
/**
* Create a JNLPFile from an input stream.
*
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(InputStream input, ParserSettings settings) throws ParseException {
this.parserSettings = settings;
parse(input, null, null);
}
/**
* Create a JNLPFile from an input stream.
*
* @param input input stream of JNLP file.
* @param codebase codebase to use if not specified in JNLP file..
* @param strict whether to enforce the spec rules
* @throws IOException if an IO exception occurred
* @throws ParseException if the JNLP file was invalid
*/
public JNLPFile(InputStream input, URL codebase, ParserSettings settings) throws ParseException {
this.parserSettings = settings;
parse(input, null, codebase);
}
/**
* Open the jnlp file URL from the cache if there, otherwise
* download to the cache. Called from constructor.
*/
private static InputStream openURL(URL location, Version version, UpdatePolicy policy) throws IOException {
if (location == null || policy == null)
throw new IllegalArgumentException(R("NullParameter"));
try {
ResourceTracker tracker = new ResourceTracker(false); // no prefetch
tracker.addResource(location, version, null, policy);
return tracker.getInputStream(location);
} catch (Exception ex) {
throw new IOException(ex.getMessage());
}
}
/**
* Returns the JNLP file's best localized title. This method returns the same
* value as InformationDesc.getTitle().
*
* Since jdk7 u45, also manifest title, and mainclass are taken to consideration;
* See PluginBridge
*/
public String getTitle() {
String jnlpTitle = getTitleFromJnlp();
String manifestTitle = getTitleFromManifest();
if (jnlpTitle != null && manifestTitle != null) {
if (jnlpTitle.equals(manifestTitle)) {
return jnlpTitle;
}
return jnlpTitle+" ("+manifestTitle+")";
}
if (jnlpTitle != null && manifestTitle == null) {
return jnlpTitle;
}
if (jnlpTitle == null && manifestTitle != null) {
return manifestTitle;
}
String mainClass = getManifestsAttributes().getMainClass();
return mainClass;
}
/**
* Returns the JNLP file's best localized title. This method returns the same
* value as InformationDesc.getTitle().
*/
public String getTitleFromJnlp() {
return getInformation().getTitle();
}
public String getTitleFromManifest() {
String inManifestTitle = getManifestsAttributes().getApplicationName();
if (inManifestTitle == null && getManifestsAttributes().isLoader()){
OutputController.getLogger().log(OutputController.Level.WARNING_ALL, TITLE_NOT_FOUND);
}
return inManifestTitle;
}
/**
* Returns the JNLP file's best localized vendor. This method returns the same
* value as InformationDesc.getVendor().
*/
public String getVendor() {
return getInformation().getVendor();
}
/**
* Returns the JNLP file's network location as specified in the
* JNLP file.
*/
public URL getSourceLocation() {
return sourceLocation;
}
/**
* Returns the location of the file parsed to create the JNLP
* file, or null if it was not created from a URL.
*/
public URL getFileLocation() {
return fileLocation;
}
/**
* Returns the location of the parent file if it exists, null otherwise
*/
public String getUniqueKey() {
return uniqueKey;
}
/**
* Returns the ParserSettings that was used to parse this file
*/
public ParserSettings getParserSettings() {
return parserSettings;
}
/**
* Returns the JNLP file's version.
*/
public Version getFileVersion() {
return fileVersion;
}
/**
* Returns the specification version required by the file.
*/
public Version getSpecVersion() {
return specVersion;
}
/**
* Returns the codebase URL for the JNLP file.
*/
public URL getCodeBase() {
return codeBase;
}
/**
* Returns the information section of the JNLP file as viewed
* through the default locale.
*/
public InformationDesc getInformation() {
return getInformation(defaultLocale);
}
/**
* Returns the information section of the JNLP file as viewed
* through the specified locale.
*/
public InformationDesc getInformation(final Locale locale) {
return new InformationDesc(new Locale[] { locale }) {
@Override
protected List