diff --git a/artifact-all-dependencies/pom.xml b/artifact-all-dependencies/pom.xml index 8ac03d348..d1999f3d8 100644 --- a/artifact-all-dependencies/pom.xml +++ b/artifact-all-dependencies/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ @@ -49,6 +49,11 @@ inet.ipaddr net.adoptopenjdk.icedteaweb.shaded.inet.ipaddr + + com.google.code.gson + net.adoptopenjdk.icedteaweb.shaded.com.google.code.gson + + org.ccil.cowan.tagsoup net.adoptopenjdk.icedteaweb.shaded.tagsoup diff --git a/artifact-no-dependencies/pom.xml b/artifact-no-dependencies/pom.xml index 50a1db3d5..aca4432b4 100644 --- a/artifact-no-dependencies/pom.xml +++ b/artifact-no-dependencies/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ diff --git a/clients/pom.xml b/clients/pom.xml index 9b092fc3d..c2285ed21 100644 --- a/clients/pom.xml +++ b/clients/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ diff --git a/common/pom.xml b/common/pom.xml index 039a51064..a8c861811 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/image/ImageGallery.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/image/ImageGallery.java new file mode 100644 index 000000000..9adf20ea1 --- /dev/null +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/image/ImageGallery.java @@ -0,0 +1,31 @@ +package net.adoptopenjdk.icedteaweb.image; + +import net.adoptopenjdk.icedteaweb.jdk89access.SunMiscLauncher; + +import javax.swing.ImageIcon; + +public enum ImageGallery { + INFO("net/adoptopenjdk/icedteaweb/image/info64.png"), + QUESTION("net/adoptopenjdk/icedteaweb/image/question64.png"), + WARNING("net/adoptopenjdk/icedteaweb/image/warn64.png"), + ERROR("net/adoptopenjdk/icedteaweb/image/error64.png"), + INFO_SMALL("net/adoptopenjdk/icedteaweb/image/info32.png"), + QUESTION_SMALL("net/adoptopenjdk/icedteaweb/image/question32.png"), + WARNING_SMALL("net/adoptopenjdk/icedteaweb/image/warn32.png"), + ERROR_SMALL("net/adoptopenjdk/icedteaweb/image/error32.png"), + LOGIN("net/adoptopenjdk/icedteaweb/image/login64.png"); + + private String path; + + ImageGallery(String path) { + this.path = path; + } + + public String path() { + return path; + } + + public ImageIcon asImageIcon() { + return SunMiscLauncher.getSecureImageIcon(path); + } +} diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/ApplicationStyleConstants.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/ApplicationStyleConstants.java new file mode 100644 index 000000000..5519328a3 --- /dev/null +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/ApplicationStyleConstants.java @@ -0,0 +1,15 @@ +package net.adoptopenjdk.icedteaweb.ui; + +import java.awt.Color; + +public interface ApplicationStyleConstants { + /* + * primary = use for text message decoration + * secondary = use for background decoration + */ + public static final Color PRIMARY_WARNING_COLOR = new Color(255, 124, 0); + public static final Color SECONDARY_WARNING_COLOR = new Color(255, 152, 0); + public static final Color PRIMARY_ERROR_COLOR = new Color(211, 47, 47); + public static final Color SECONDARY_ERROR_COLOR = new Color(244, 67, 54); + +} diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogButton.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogButton.java new file mode 100644 index 000000000..4516bf6f8 --- /dev/null +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogButton.java @@ -0,0 +1,68 @@ +package net.adoptopenjdk.icedteaweb.ui.dialogs; + +import net.adoptopenjdk.icedteaweb.Assert; + +import javax.swing.JButton; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class DialogButton { + + private final String text; + + private final Supplier onAction; + + private final String description; + + private boolean enabled = true; + + private List> enabledObservers = new CopyOnWriteArrayList<>(); + + public DialogButton(final String text, final Supplier onAction) { + this(text, onAction, null); + } + + public DialogButton(final String text, final Supplier onAction, final String description) { + this.text = Assert.requireNonBlank(text, "text"); + this.onAction = Assert.requireNonNull(onAction, "onAction"); + this.description = description; + } + + public String getDescription() { + return description; + } + + public String getText() { + return text; + } + + public Supplier getOnAction() { + return onAction; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + enabledObservers.forEach(o -> o.accept(enabled)); + } + + public boolean isEnabled() { + return enabled; + } + + public JButton createButton(Consumer actionResultConsumer) { + final JButton button = new JButton(getText()); + if (getDescription() != null) { + button.setToolTipText(getDescription()); + } + button.addActionListener(e -> { + final R result = getOnAction().get(); + Optional.ofNullable(actionResultConsumer).ifPresent(c -> c.accept(result)); + }); + enabledObservers.add(b -> button.setEnabled(b)); + button.setEnabled(isEnabled()); + return button; + } +} diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogWithResult.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogWithResult.java new file mode 100644 index 000000000..1fc26dd39 --- /dev/null +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/dialogs/DialogWithResult.java @@ -0,0 +1,62 @@ +package net.adoptopenjdk.icedteaweb.ui.dialogs; + +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import java.awt.Dialog; +import java.awt.Dimension; +import java.util.concurrent.CompletableFuture; + +public abstract class DialogWithResult extends JDialog { + + private R result; + + public DialogWithResult() { + this((Dialog) null); + } + + public DialogWithResult(final Dialog owner) { + super(owner, true); + setModalityType(ModalityType.APPLICATION_MODAL); + setResizable(true); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + } + + protected abstract String createTitle(); + + protected abstract JPanel createContentPane(); + + protected void close(final R result) { + this.result = result; + this.setVisible(false); + this.dispose(); + } + + public Dimension getPreferredSize() { + return new Dimension(800, super.getPreferredSize().height); + } + + public R showAndWait() { + if (SwingUtilities.isEventDispatchThread()) { + setTitle(createTitle()); + getContentPane().removeAll(); + getContentPane().add(createContentPane()); + pack(); + setLocationRelativeTo(null); + setVisible(true); + return result; + } else { + final CompletableFuture result = new CompletableFuture<>(); + try { + SwingUtilities.invokeAndWait(() -> { + pack(); + final R r = showAndWait(); + result.complete(r); + }); + return result.get(); + } catch (Exception e) { + throw new RuntimeException("Error in handling dialog!", e); + } + } + } +} diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/SwingUtils.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/SwingUtils.java index 49250288e..b1cd8329d 100644 --- a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/SwingUtils.java +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/SwingUtils.java @@ -36,6 +36,7 @@ import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import javax.swing.JLabel; import javax.swing.JWindow; import javax.swing.SwingUtilities; import java.awt.Component; @@ -179,4 +180,15 @@ public void run() { } return window; } + + /** + * Some Swing components such as {@link JLabel} needs their text to be wrapped as HTML to get word wrap working. + * This method allows to wrap a given text with an html tag. + * + * @param s string to be wrapped to html tag + * @return + */ + public static String htmlWrap(String s) { + return "" + s + ""; + } } diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturn.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturn.java index 160ec7e35..88ade5c6c 100644 --- a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturn.java +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturn.java @@ -34,7 +34,6 @@ package net.adoptopenjdk.icedteaweb.ui.swing.dialogresults; import java.util.EnumSet; -import java.util.Objects; public class AccessWarningPaneComplexReturn implements DialogResult { @@ -82,97 +81,6 @@ public static String allValues() { } } - public static class ShortcutResult { - - public static ShortcutResult readValue(String s) { - if (s.trim().isEmpty()) { - return null; - } - String[] sq = s.split(","); - ShortcutResult sr = new ShortcutResult(Boolean.valueOf(sq[3])); - sr.browser = sq[0]; - sr.fixHref = Boolean.parseBoolean(sq[1]); - if (!sq[2].equalsIgnoreCase("null")) { - sr.shortcutType = Shortcut.valueOf(sq[2]); - } - return sr; - } - - public String writeValue() { - StringBuilder sb = new StringBuilder(); - sb.append(browser).append(",") - .append(fixHref).append(",") - .append(shortcutType).append(",") - .append(create).append(","); - return sb.toString(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ShortcutResult)) { - return false; - } - ShortcutResult sr = (ShortcutResult) obj; - return this.create == sr.create && this.fixHref == sr.fixHref - && this.browser.equals(sr.browser) && this.shortcutType == sr.shortcutType; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 89 * hash + Objects.hashCode(this.browser); - hash = 89 * hash + (this.fixHref ? 1 : 0); - hash = 89 * hash + Objects.hashCode(this.shortcutType); - hash = 89 * hash + (this.create ? 1 : 0); - return hash; - } - - private String browser = "not_found_browser"; - private boolean fixHref = false; - private Shortcut shortcutType = null; - private final boolean create; - - ShortcutResult(String browser, boolean fixHref, Shortcut shortcutType, boolean create) { - this.browser = browser; - this.fixHref = fixHref; - this.shortcutType = shortcutType; - this.create = create; - } - - public ShortcutResult(boolean create) { - this.create = create; - } - - public boolean isCreate() { - return create; - } - - public String getBrowser() { - return browser; - } - - public Shortcut getShortcutType() { - return shortcutType; - } - - public boolean isFixHref() { - return fixHref; - } - - public void setBrowser(String browser) { - this.browser = browser; - } - - public void setFixHref(boolean fixHref) { - this.fixHref = fixHref; - } - - public void setShortcutType(Shortcut shortcutType) { - this.shortcutType = shortcutType; - } - - } - private final YesNo regularReturn; private ShortcutResult desktop; private ShortcutResult menu; diff --git a/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/ShortcutResult.java b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/ShortcutResult.java new file mode 100644 index 000000000..88c13469d --- /dev/null +++ b/common/src/main/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/ShortcutResult.java @@ -0,0 +1,94 @@ +package net.adoptopenjdk.icedteaweb.ui.swing.dialogresults; + +import java.util.Objects; + +public class ShortcutResult { + + public static ShortcutResult readValue(String s) { + if (s.trim().isEmpty()) { + return null; + } + String[] sq = s.split(","); + ShortcutResult sr = new ShortcutResult(Boolean.valueOf(sq[3])); + sr.browser = sq[0]; + sr.fixHref = Boolean.parseBoolean(sq[1]); + if (!sq[2].equalsIgnoreCase("null")) { + sr.shortcutType = AccessWarningPaneComplexReturn.Shortcut.valueOf(sq[2]); + } + return sr; + } + + public String writeValue() { + StringBuilder sb = new StringBuilder(); + sb.append(browser).append(",") + .append(fixHref).append(",") + .append(shortcutType).append(",") + .append(create).append(","); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ShortcutResult)) { + return false; + } + ShortcutResult sr = (ShortcutResult) obj; + return this.create == sr.create && this.fixHref == sr.fixHref + && this.browser.equals(sr.browser) && this.shortcutType == sr.shortcutType; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 89 * hash + Objects.hashCode(this.browser); + hash = 89 * hash + (this.fixHref ? 1 : 0); + hash = 89 * hash + Objects.hashCode(this.shortcutType); + hash = 89 * hash + (this.create ? 1 : 0); + return hash; + } + + private String browser = "not_found_browser"; + private boolean fixHref = false; + private AccessWarningPaneComplexReturn.Shortcut shortcutType = null; + private final boolean create; + + ShortcutResult(String browser, boolean fixHref, AccessWarningPaneComplexReturn.Shortcut shortcutType, boolean create) { + this.browser = browser; + this.fixHref = fixHref; + this.shortcutType = shortcutType; + this.create = create; + } + + public ShortcutResult(boolean create) { + this.create = create; + } + + public boolean isCreate() { + return create; + } + + public String getBrowser() { + return browser; + } + + public AccessWarningPaneComplexReturn.Shortcut getShortcutType() { + return shortcutType; + } + + public boolean isFixHref() { + return fixHref; + } + + public void setBrowser(String browser) { + this.browser = browser; + } + + public void setFixHref(boolean fixHref) { + this.fixHref = fixHref; + } + + public void setShortcutType(AccessWarningPaneComplexReturn.Shortcut shortcutType) { + this.shortcutType = shortcutType; + } + +} diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties index 48feb7cb2..6eeee74df 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages.properties @@ -9,8 +9,10 @@ # General NullParameter=Null parameter ButAllow=Allow +ButCreate=Create +ButDeny=Deny ButBrowse=Browse... -ButCancel=\ Cancel\ +ButCancel=Cancel ButClose=Close ButAdvancedOptions=Advanced Options ButLunchFullItwSettings=Launch full settings @@ -32,6 +34,8 @@ BUTback=Back BUTforward=Forward BUTreload=Reload ITWdocsMissingAuthors=See authors file +AndMore=and {0} more. +MoreInformation=More Information HEADLESS_MISCONFIGURED=Headless check failed. You are forced to run without any graphics. IcedTea-Web can run like this, but your app probably not. This is likely a bug in your system. @@ -54,12 +58,17 @@ Continue=Do you want to continue? Field=Field From=From Name=Name -Password=Password: +Password=Password Publisher=Publisher Unknown= -Username=Username: +Username=Username Value=Value Version=Version +Application=Application +ApplicationDetails=Application Details +Codebase=Codebase +SourceLocation=SourceLocation +Login=Login # about dialogue AboutDialogueTabAbout=About @@ -74,6 +83,14 @@ JREContinueDialogSentence2=Do you want to continue running? JREContinueDialogSentenceTitle=Incompatible JRE # missing permissions dialogue +MissingPermissionsAttribute=Missing Permissions Attribute +MissingPermissionsAttributeMessage=Application is missing the permissions attribute and is therefore not trustworthy. \ +Do you wish to allow this application to run? +MissingPermissionsAttributeMoreInfo=For more information see: MissingPermissionsMainTitle=Application {0} \ from {1} is missing the permissions attribute. \ Applications without this attribute should not be trusted. Do you wish to allow this application to run? @@ -84,6 +101,15 @@ and
\ +JAR File Manifest Attributes \ +
  • \ +Preventing Applications from Being Repurposed
  • ALACAMissingMainTitle=The application {0} \ from {1} uses resources from the following remote locations: \ {2} \ @@ -95,6 +121,16 @@ and
    \ +JAR File Manifest Attributes \ +
  • \ +Preventing Applications from Being Repurposed
  • + ALACAMatchingMainTitle=The application {0} \ from {1} uses resources from the following remote locations:
    {2}
    \ They look ok. Are you sure you want to run this application? @@ -472,16 +508,21 @@ SPrinterAccess=The application has requested printer access. Do you want to allo SNetworkAccess=The application has requested permission to establish connections to {0}. Do you want to allow this action? SNoAssociatedCertificate= SUnverified=(unverified) +SUnverifiedJnlp=The following details are not signed and thus are not trustworthy! SAlwaysTrustPublisher=Always trust content from this publisher +SSecurityApprovalRequired=Security Approval Required +SSecurityWarning=Security Warning SHttpsUnverified=The website''s HTTPS certificate cannot be verified. SRememberOption=Remember this option? SRememberAppletOnly=For applet SRememberCodebase=For site {0} +SUnsignedApplication=Unsigned Application SUnsignedSummary=An unsigned application wants to run SUnsignedDetail=An unsigned application from the following location wants to run:
      {0}
    The page which made the request was:
      {1}

    It is recommended you only run applications from sites you trust. SUnsignedAllowedBefore=You have accepted this applet previously - ({0}). SUnsignedRejectedBefore=You have rejected this applet previously - ({0}). SUnsignedQuestion=Allow the applet to run? +SPartiallySignedApplication=Partially Signed Application SPartiallySignedSummary=Only parts of this application code are signed. SPartiallySignedDetail=This application contains both signed and unsigned code. While signed code is safe if you trust the provider, unsigned code may imply code outside of the trusted provider''s control. SPartiallySignedQuestion=Do you wish to proceed and run this application anyway? @@ -496,6 +537,7 @@ STempReadFilesAndProperties=Read-only access to all files and properties STempWriteFilesAndProperties=Write-only access to all files and properties STempReflectionAndExternal=Reflection and external code access STempAllMedia=All media (printing, audio, clipboard access) +SUntrustedRecommendation=It is recommended you only run applications from sites you trust. # Security - used for the More Information dialog SBadKeyUsage=Resources contain entries whose signer certificate''s KeyUsage extension doesn''t allow code signing. @@ -670,17 +712,17 @@ CPButAdvancedEditor=Advanced editor... # Control Panel - Headers CPHead=IcedTea-Web Control Panel -CPHeadAbout=\u00a0About\u00a0IcedTea-Web\u00a0 -CPHeadNetworkSettings=\u00a0Network\u00a0Proxy\u00a0Settings\u00a0 -CPHeadTempInternetFiles=\u00a0Temporary\u00a0Internet\u00a0Files\u00a0 -CPHeadJRESettings=\u00a0JRE\u00a0Settings\u00a0 -CPHeadCertificates=\u00a0Certificates\u00a0 -CPHeadDebugging=\u00a0Debugging\u00a0Settings\u00a0 -CPHeadDesktopIntegration=\u00a0Desktop\u00a0Integrations\u00a0 -CPHeadSecurity=\u00a0Security\u00a0Settings\u00a0 -CPHeadJVMSettings=\u00a0JVM\u00a0Settings\u00a0 -CPHeadPolicy=\u00a0Custom\u00a0Policy\u00a0Settings\u00a0 -CPServerWhitelist=\u00a0Server\u00a0Whitelist\u00a0 +CPHeadAbout=\u00A0About\u00A0IcedTea-Web\u00A0 +CPHeadNetworkSettings=\u00A0Network\u00A0Proxy\u00A0Settings\u00A0 +CPHeadTempInternetFiles=\u00A0Temporary\u00A0Internet\u00A0Files\u00A0 +CPHeadJRESettings=\u00A0JRE\u00A0Settings\u00A0 +CPHeadCertificates=\u00A0Certificates\u00A0 +CPHeadDebugging=\u00A0Debugging\u00A0Settings\u00A0 +CPHeadDesktopIntegration=\u00A0Desktop\u00A0Integrations\u00A0 +CPHeadSecurity=\u00A0Security\u00A0Settings\u00A0 +CPHeadJVMSettings=\u00A0JVM\u00A0Settings\u00A0 +CPHeadPolicy=\u00A0Custom\u00A0Policy\u00A0Settings\u00A0 +CPServerWhitelist=\u00A0Server\u00A0Whitelist\u00A0 # Control Panel - Tabs CPTabAbout=About IcedTea-Web @@ -1034,10 +1076,10 @@ SGPUseTLS1=Use TLS 1.0 (Unsupported) # Control Panel - TemporaryInternetFilesPanel TIFPEnableCache=Keep temporary files on my computer -TIFPLocation=\u00a0Location\u00a0 +TIFPLocation=\u00A0Location\u00A0 TIFPLocationLabel=Select the location where temporary files are kept TIFPChange=Change -TIFPDiskSpace=\u00a0Disk\u00a0space\u00a0 +TIFPDiskSpace=\u00A0Disk\u00A0space\u00A0 TIFPCompressionLevel=Select the compression level for JAR files TIFPNone=None TIFPMax=Max diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_cs.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_cs.properties index 2d1ecf5ca..411a9c25c 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_cs.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_cs.properties @@ -54,10 +54,10 @@ Continue=Chcete pokra\u010dovat? Field=Pole From=Od Name=N\u00e1zev -Password=Heslo: +Password=Heslo Publisher=Vydavatel Unknown= -Username=U\u017eivatelsk\u00e9 jm\u00e9no: +Username=U\u017eivatelsk\u00e9 jm\u00e9no Value=Hodnota Version=Verze diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties index 3505109a9..da8df8d6a 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_de.properties @@ -27,6 +27,8 @@ ButYes=Ja ButNo=Nein BUTControlledBy=Gesteuert durch {0} BUTmodified=modifiziert +AndMore=und {0} weitere. +MoreInformation=Weitere Informationen CertWarnRunTip=Diesem Applet vertrauen und mit vollen Berechtigungen ausf\u00FChren CertWarnSandboxTip=Diesem Applet nicht vertrauen und mit eingeschr\u00E4nkten Berechtigungen ausf\u00FChren @@ -47,12 +49,15 @@ Continue=Soll fortgefahren werden? Field=Feld From=Von Name=Name -Password=Kennwort: +Password=Kennwort Publisher=Herausgeber Unknown= -Username=Benutzername: +Username=Benutzername Value=Wert Version=Version +Application=Application +ApplicationDetails=Anwendungsdetails +Login=Login # about dialogue AboutDialogueTabAbout=\u00DCber @@ -78,12 +83,30 @@ und
    \ \ Preventing the repurposing of Applications +MissingPermissionsAttribute=Fehlende Berechtigungsangaben +MissingPermissionsAttributeMessage=Der Anwendung fehlen die Angaben zu Berechtigungen und sie ist daher nicht \ +vertrauensw\u00FCrdig. Soll die Anwendung dennoch ausgef\u00FChrt werden? +MissingPermissionsAttributeMoreInfo=Weitere Informationen finden sie hier: + # missing Application-Library-Allowable-Codebase dialogue +MissingALACAttribute=Fehlendes ALAC-Attribut im Manifest +MissingALACAttributeLocationListTitle=Die Anwendung verwendet Resourcen von folgenden Remotedom\u00E4nen: +MissingALACAttributeMessage=Es ist aufgrund des fehlenden ALAC-Manifest-Attributes nicht m\u00F6glich, den Ursprungsort \ +der Anwendung zu \u00FCberpr\u00FCfen. Soll diese Anwendung wirklich ausgef\u00FChrt werden? +MissingALACAttributeMoreInfo=Weitere Informationen finden sie hier: ALACAMissingMainTitle=Die Anwendung \u201E{0}\u201C \ mit der Codebasis \u201E{1}\u201C l\u00E4dt die folgenden Ressourcen von einer fremden Dom\u00E4ne:
    \ {2}
    \ Soll diese Anwendung wirklich ausgef\u00FChrt werden? -ALACAMissingInfo=Um weitere Informationen zu erhalten siehe:
    \ +ALACAMissingInfo=Weitere Informationen finden sie hier:
    \ \ JAR File Manifest Attributes
    \ und
    \ @@ -91,6 +114,15 @@ und
    \ Preventing the Repurposing of an Application # matching Application-Library-Allowable-Codebase dialogue +MatchingALACAttribute=\u00DCbereinstimmung mit ALAC-Attribut im Manifest +MatchingALACAttributeLocationListTitle=Die Anwendung verwendet Resourcen von folgenden Remotedom\u00E4nen: +MatchingALACAttributeMessage=Die Anwendung verwendet non-codebase Resourcen welche mit dem ALAC-Attribut im Manifest \u00FCbereinstimmen. \ +Soll die Anwendung ausgef\u00FChrt werden? +MatchingALACAttributeMoreInfo=Um weitere Informationen zu erhalten siehe: ALACAMatchingMainTitle=Die Anwendung \u201E{0}\u201C \ mit der Codebasis \u201E{1}\u201C l\u00E4dt die folgenden Ressourcen von einer fremden Dom\u00E4ne:
    \ {2}
    \ @@ -409,8 +441,8 @@ EXAWmenuWants=Symbolverkn\u00FCpfung im Men\u00FC (durch die Anwendung vorgesehe EXAWmenuDontWants=Symbolverkn\u00FCpfung im Men\u00FC (durch die Anwendung nicht vorgesehen, aber auf Benutzeranforderung m\u00F6glich). EXAWsettingsInfo=\u201E{0}\u201C ist eingestellt. Es kann \u00FCber das Panel \u201E{1}\u201C in itweb-settings ge\u00E4ndert werden. EXAWsettingsManage=Bestehende Men\u00FCeintr\u00E4ge k\u00F6nnen \u00FCber das Panel \u201E{0}\u201C in itweb-settings verwaltet werden. -EXAWrememberByApp=Per Anwendung merken -EXAWrememberByPage=Per Dom\u00E4ne merken +EXAWrememberByApp=F\u00FCr Anwendung merken +EXAWrememberByPage=F\u00FCr Dom\u00E4ne merken EXAWdontRemember=Nicht merken EXAWrememberByAppTooltip=Diese Anwendung wird nie nach weiteren Berechtigungen fragen EXAWrememberByPageTooltip=Alle Anwendungen in dieser Dom\u00E4ne werden nicht mehr nach Berechtigungen fragen, sondern die bestehenden Berechtigungen verwenden @@ -442,7 +474,7 @@ EXAWfixTolltip=Manche von einem Applet verwiesene JNLP-Dateien wurden nich # Security SFileReadAccess=Die Anwendung hat Lesezugriff auf {0} angefordert. Soll diese Aktion zugelassen werden? SFileWriteAccess=Die Anwendung hat Schreibzugriff auf {0} angefordert. Soll diese Aktion zugelassen werden? -SFileReadWriteAccess=Die Anwendung hat Lese- & Schreibzugriff auf {0} angefordert. Soll diese Aktion zugelassen werden? +SFileReadWriteAccess=Die Anwendung hat Lese- und Schreibzugriff auf {0} angefordert. Soll diese Aktion zugelassen werden? SDesktopShortcut=Die Anwendung hat die Berechtigung eine Verkn\u00FCpfung auf dem Desktop zu erstellen angefordert. Soll diese Aktion zugelassen werden? SSigUnverified=Die digitale Signatur der Anwendung kann nicht verifiziert werden. Soll die Anwendung zur Ausf\u00FChrung gebracht werden? Sie erh\u00E4lt unbeschr\u00E4nkten Zugriff auf den Computer. SSigVerified=Die digitale Signatur der Anwendung wurde verifiziert. Soll die Anwendung zur Ausf\u00FChrung gebracht werden? Sie erh\u00E4lt unbeschr\u00E4nkten Zugriff auf den Computer. @@ -456,16 +488,21 @@ SPrinterAccess=Die Anwendung hat den Druckerzugriff angefordert. Soll diese Akti SNetworkAccess=Die Anwendung hat die Berechtigung Verbindungen zu {0} herzustellen angefordert. Soll diese Aktion zugelassen werden? SNoAssociatedCertificate= SUnverified=(nicht verifiziert) +SUnverifiedJnlp=Die folgenden Angaben sind nicht signiert und daher nicht vertrauensw\u00FCrdig! SAlwaysTrustPublisher=Dem Inhalt von diesem Herausgeber immer vertrauen +SSecurityApprovalRequired=Sicherheitsbest\u00E4htigung notwendig +SSecurityWarning=Sicherheitswarnung SHttpsUnverified=Das HTTPS Zertifikat dieser Website kann nicht verifiziert werden. SRememberOption=Soll diese Option gespeichert werden? SRememberAppletOnly=F\u00FCr Applet SRememberCodebase=F\u00FCr Website {0} -SUnsignedSummary=Eine nicht signierte Anwendung m\u00F6chte zur Ausf\u00FChrung gebracht werden. -SUnsignedDetail=Eine nicht signierte Anwendung am folgenden Ort m\u00F6chte zur Ausf\u00FChrung gebracht werden:
      {0}
    Seite, welche die Anforderung gestellt hat:
      {1}

    Es wird empfohlen, ausschlie\u00DFlich Anwendungen zur Ausf\u00FChrung zu bringen, die von vertrauensw\u00FCrdigen Websites stammen. +SUnsignedApplication=Unsignierte Anwendung +SUnsignedSummary=Eine unsignierte Anwendung m\u00F6chte zur Ausf\u00FChrung gebracht werden. +SUnsignedDetail=Eine unsignierte Anwendung am folgenden Ort m\u00F6chte zur Ausf\u00FChrung gebracht werden:
      {0}
    Seite, welche die Anforderung gestellt hat:
      {1}

    Es wird empfohlen, ausschlie\u00DFlich Anwendungen zur Ausf\u00FChrung zu bringen, die von vertrauensw\u00FCrdigen Websites stammen. SUnsignedAllowedBefore=Dieses Applet wurde bereits akzeptiert ({0}). SUnsignedRejectedBefore=Dieses Applet wurde bereits abgelehnt ({0}). SUnsignedQuestion=Soll dem Applet die Ausf\u00FChrung erlaubt werden? +SPartiallySignedApplication=Teilweise signierte Anwendung SPartiallySignedSummary=Nur Teile des Anwendungscodes sind signiert. SPartiallySignedDetail=Diese Anwendung enth\u00E4lt sowohl signierten als auch nicht signierten Code. W\u00E4hrend signierter Code sicher ist, wenn Sie dem Anbieter vertrauen, kann nicht signierter Code sich \u00FCber Code erstrecken, der sich der Kontrolle des Anbieters entzieht. SPartiallySignedQuestion=Soll fortgefahren und diese Anwendung dennoch zur Ausf\u00FChrung gebracht werden? diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_pl.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_pl.properties index dacfac205..0bea6c940 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_pl.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_pl.properties @@ -36,10 +36,10 @@ Continue=Czy chcesz kontynuowa\u0107? Field=Pole From=Od Name=Nazwa -Password=Has\u0142o: +Password=Has\u0142o Publisher=Wydawca Unknown= -Username=U\u017cytkownik: +Username=U\u017cytkownik Value=Warto\u015b\u0107 Version=Wersja diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_ru.properties b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_ru.properties index 9ce453746..4483121ef 100644 --- a/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_ru.properties +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/i18n/Messages_ru.properties @@ -54,10 +54,10 @@ Continue=\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c? Field=\u041f\u043e\u043b\u0435 From=\u041e\u0442 Name=\u0418\u043c\u044f -Password=\u041f\u0430\u0440\u043e\u043b\u044c: +Password=\u041f\u0430\u0440\u043e\u043b\u044c Publisher=\u0418\u0437\u0434\u0430\u0442\u0435\u043b\u044c Unknown=<\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e> -Username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f: +Username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Value=\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 Version=\u0412\u0435\u0440\u0441\u0438\u044f diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/ImageDesignTemplate.svg b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/ImageDesignTemplate.svg new file mode 100644 index 000000000..cd8b19f32 --- /dev/null +++ b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/ImageDesignTemplate.svg @@ -0,0 +1,2350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 500 + + + + + + 300 + + + + + + + + + i + i + ? + + + + + ? + + + + + i + + + + + + + + + 700 + + + + + + + This SVG is the design template for the dialog images and icons. You can use this as base to design more icons in the future. This document was created using Inkscape 1.0. + + + DRAFTS (Work in Progress) + FINALS (Ready to Export) + + DESIGN PARAMETER (Color, Font, Size) + + + X + + + + X + + + + + X + + + diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error32.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error32.png new file mode 100644 index 000000000..e739821ee Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error32.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error64.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error64.png new file mode 100644 index 000000000..e5bed2b24 Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/error64.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info32.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info32.png new file mode 100644 index 000000000..2d9eabb99 Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info32.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info64.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info64.png new file mode 100644 index 000000000..9dd177fbd Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/info64.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/login64.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/login64.png new file mode 100644 index 000000000..f6ccc9d6b Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/login64.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question32.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question32.png new file mode 100644 index 000000000..ba7e45a1b Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question32.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question64.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question64.png new file mode 100644 index 000000000..7ad1f1dd1 Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/question64.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn32.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn32.png new file mode 100644 index 000000000..92d236bd0 Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn32.png differ diff --git a/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn64.png b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn64.png new file mode 100644 index 000000000..e526e4c21 Binary files /dev/null and b/common/src/main/resources/net/adoptopenjdk/icedteaweb/image/warn64.png differ diff --git a/core/pom.xml b/core/pom.xml index a9b803a06..072435ca7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ @@ -49,6 +49,11 @@ js 1.7R1 + + com.google.code.gson + gson + 2.8.5 + ${project.groupId} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidator.java new file mode 100644 index 000000000..7947f4403 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidator.java @@ -0,0 +1,19 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader.LoadableJar; + +import java.util.List; + +/** + * Responsible for validating the trust we have in the application. + *
      + *
    • Jar Signatures
    • + *
    • JNLP Signature
    • + *
    • Certificates used for signing
    • + *
    • Content of manifest
    • + *
    + */ +public interface ApplicationTrustValidator { + void validateEagerJars(List jars); + void validateLazyJars(List jars); +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidatorImpl.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidatorImpl.java new file mode 100644 index 000000000..3444c8963 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ApplicationTrustValidatorImpl.java @@ -0,0 +1,251 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader.LoadableJar; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; +import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader; +import net.adoptopenjdk.icedteaweb.resources.cache.Cache; +import net.adoptopenjdk.icedteaweb.security.RememberingSecurityUserInteractions; +import net.adoptopenjdk.icedteaweb.security.SecurityUserInteractions; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPMatcher; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.ApplicationManager; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.signing.NewJarCertVerifier; +import net.sourceforge.jnlp.signing.SignVerifyUtils; +import net.sourceforge.jnlp.tools.CertInformation; + +import java.io.File; +import java.net.URISyntaxException; +import java.security.cert.CertPath; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.SANDBOX; +import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; + +/** + * See {@link ApplicationTrustValidator}. + */ +public class ApplicationTrustValidatorImpl implements ApplicationTrustValidator { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationTrustValidatorImpl.class); + + private final ZonedDateTime now = ZonedDateTime.now(); + private final SecurityUserInteractions userInteractions; + + private final NewJarCertVerifier certVerifier; + private final JNLPFile jnlpFile; + + private boolean allowUnsignedApplicationToRun = false; // set to true if the user accepts to run unsigned application + private CertPath trustedCertificate = null; // certificate which was confirmed by the user + + + public ApplicationTrustValidatorImpl(final JNLPFile jnlpFile) { + this(jnlpFile, new RememberingSecurityUserInteractions()); + } + + public ApplicationTrustValidatorImpl(final JNLPFile jnlpFile, final SecurityUserInteractions userInteractions) { + this.jnlpFile = jnlpFile; + this.certVerifier = new NewJarCertVerifier(); + this.userInteractions = userInteractions; + } + + /** + * Use the certVerifier to find certificates which sign all jars + * + *
    +     * - no certificate which signs all jars
    +     *      -> if main jar is signed by a certificate
    +     *          -> find the certificate with the least (or no) problems (remember this decision)
    +     *          -> if certificate has problems ask user to trust certificate
    +     *          -> check if the JNLP file is signed
    +     *      -> ask user for permission to run unsigned application
    +     * - one or more certificates which sign all jars
    +     *      -> find the certificate with the least (or no) problems (remember this decision)
    +     *      -> if certificate has problems ask user to trust certificate
    +     *      -> check if the JNLP file is signed
    +     * 
    + * + * @param jars the new jars to add. + */ + @Override + public void validateEagerJars(List jars) { + try { + validateJars(jars, true); + + } catch (LaunchException e) { + LOG.error("Abort application launch - {}", e.getMessage()); + JNLPRuntime.getDefaultLaunchHandler().handleLaunchError(e); + JNLPRuntime.exit(-1); + } + } + + + /** + * Use the certVerifier to find certificates which sign all jars + * + *
    +     * - new jar is unsigned or signed by a certificate which does not sign all other jars
    +     *      -> ask user for permission to run unsigned application
    +     * - new jar is signed by the remembered certificate
    +     *      -> OK
    +     * - new jar is signed by a certificate which also signs all other jars and has no issues
    +     *      -> change remembered decision
    +     * - new jar is signed by a certificate which also signs all other jars and has issues
    +     *      -> ask user to trust certificate -> change remembered decision
    +     * 
    + * + * @param jars the new jars to add. + */ + @Override + public void validateLazyJars(List jars) { + try { + validateJars(jars, false); + } catch (LaunchException e) { + LOG.info("Abort application - {}", e.getMessage()); + JNLPRuntime.exit(0); + } + } + + private void validateJars(List jars, boolean mustContainMainJar) throws LaunchException { + final ApplicationInstance applicationInstance = ApplicationManager.getApplication(jnlpFile) + .orElseThrow(() -> new IllegalStateException("No ApplicationInstance found for " + jnlpFile.getTitleFromJnlp())); + + if (applicationInstance.getApplicationEnvironment() == SANDBOX || allowUnsignedApplicationToRun) { + return; + } + + certVerifier.addAll(toFiles(jars)); + + File mainJarFile = null; + if (mustContainMainJar) { + mainJarFile = getMainJarFile(jars, jnlpFile); + markJnlpAsSignedIfContainedInMainJarAndMainJarIsSignedByTrustedCertificate(jnlpFile, mainJarFile); + } + + if (certVerifier.isNotFullySigned()) { + if (userInteractions.askUserForPermissionToRunUnsignedApplication(jnlpFile) != AllowDeny.ALLOW) { + throw new LaunchException("User declined running unsigned application"); + } + allowUnsignedApplicationToRun = true; + } else { + final Set fullySigningCertificates = certVerifier.getFullySigningCertificates(); + if (!hasTrustedCertificate(fullySigningCertificates)) { + final Map certInfos = calculateCertInfo(fullySigningCertificates); + + for (final Map.Entry entry : certInfos.entrySet()) { + final AllowDenySandbox result = userInteractions.askUserHowToRunApplicationWithCertIssues(jnlpFile, entry.getKey(), entry.getValue()); + + if (result == AllowDenySandbox.SANDBOX) { + applicationInstance.setApplicationEnvironment(SANDBOX); + return; + } else if (result == AllowDenySandbox.ALLOW) { + trustedCertificate = entry.getKey(); + if (mustContainMainJar) { + markJnlpAsSignedIfContainedInMainJarAndMainJarIsSignedByTrustedCertificate(jnlpFile, mainJarFile); + } + break; + } + } + + if (!hasTrustedCertificate(fullySigningCertificates)) { + throw new LaunchException("User exited application when asked to how to run application with certificate issues"); + } + } + } + + if (mustContainMainJar) { + new ManifestAttributesChecker(jnlpFile, !certVerifier.isNotFullySigned(), new ManifestAttributesReader(mainJarFile)).checkAll(); + } + } + + private static List toFiles(List jars) { + return jars.stream() + .map(ApplicationTrustValidatorImpl::toFile) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + private static Optional toFile(LoadableJar loadableJar) { + return loadableJar.getLocation() + .map(location -> { + try { + return new File(location.toURI()); + } catch (URISyntaxException e) { + LOG.warn("URISyntaxException for url '{}'", location); + return null; + } + }); + } + + + private File getMainJarFile(List jars, JNLPFile file) throws LaunchException { + final JARDesc mainJarDesc = file.getResources().getMainJAR(); + + if (mainJarDesc == null) { + throw new LaunchException("no main jar defined"); + } + + final LoadableJar mainJar = jars.stream() + .filter(jar -> Objects.equals(jar.getJarDesc(), mainJarDesc)) + .findFirst() + .orElseThrow(() -> { + LOG.debug("Main jar {} not found in {}", mainJarDesc, jars); + return new LaunchException("Could not find main jar among the eager jars"); + }); + return toFile(mainJar).orElseThrow(() -> new LaunchException("Could not find/download main jar file.")); + } + + private void markJnlpAsSignedIfContainedInMainJarAndMainJarIsSignedByTrustedCertificate(final JNLPFile file, final File mainJarFile) { + if (file.isUnsigend() && isJarSignedByTrustedCertificate(mainJarFile)) { + final JNLPMatcher jnlpMatcher = new JNLPMatcher(mainJarFile, getLocalFile(file), file.getParserSettings()); + if (jnlpMatcher.isMatch()) { + file.markFileAsSigned(); + } + } + } + + private boolean isJarSignedByTrustedCertificate(final File jarFile) { + final Set certPaths = certVerifier.certificatesSigning(jarFile).getCertificatePaths(); + return hasTrustedCertificate(certPaths); + } + + private boolean hasTrustedCertificate(final Set certPaths) { + + final Map certInfos = calculateCertInfo(certPaths); + + final boolean hasTrustedCertificate = certInfos.values().stream() + .filter(infos -> infos.isRootInCacerts() || infos.isPublisherAlreadyTrusted()) + .anyMatch(infos -> !infos.hasSigningIssues()); + + return hasTrustedCertificate || certInfos.containsKey(trustedCertificate); + } + + private Map calculateCertInfo(Set certPaths) { + return certPaths.stream() + .collect(Collectors.toMap(Function.identity(), certPath -> SignVerifyUtils.calculateCertInformationFor(certPath, now))); + } + + private static File getLocalFile(final JNLPFile file) { + // If the file is on the local file system, use original path, otherwise find cached file + if (file.getFileLocation().getProtocol().toLowerCase().equals(FILE_PROTOCOL)) { + return new File(file.getFileLocation().getPath()); + } else { + return Cache.getCacheFile(file.getFileLocation(), file.getFileVersion()); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ClassLoaderUtils.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ClassLoaderUtils.java new file mode 100644 index 000000000..ed6479f47 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/ClassLoaderUtils.java @@ -0,0 +1,22 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class ClassLoaderUtils { + + private static final Executor BACKGROUND_EXECUTOR = Executors.newCachedThreadPool(); + + public static Executor getClassloaderBackgroundExecutor() { + return BACKGROUND_EXECUTOR; + } + + public static V waitForCompletion(Future f, String message) { + try { + return f.get(); + } catch (final Exception e) { + throw new RuntimeException(message, e); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Extension.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Extension.java new file mode 100644 index 000000000..ed739842a --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Extension.java @@ -0,0 +1,38 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import java.net.URL; +import java.util.Objects; + +public class Extension { + + private final URL extensionLocation; + + private final String version; + + public Extension(final URL extensionLocation, final String version) { + this.extensionLocation = extensionLocation; + this.version = version; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Extension extension = (Extension) o; + return Objects.equals(extensionLocation, extension.extensionLocation) && + Objects.equals(version, extension.version); + } + + @Override + public int hashCode() { + return Objects.hash(extensionLocation, version); + } + + @Override + public String toString() { + return "Extension{" + + "extensionLocation=" + extensionLocation + + ", version='" + version + '\'' + + '}'; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoader.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoader.java new file mode 100644 index 000000000..84a0b024d --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoader.java @@ -0,0 +1,157 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.locks.ReentrantLock; + +public class JnlpApplicationClassLoader extends URLClassLoader { + + private static final Logger LOG = LoggerFactory.getLogger(JnlpApplicationClassLoader.class); + + private final JarProvider jarProvider; + private final NativeLibrarySupport nativeLibrarySupport; + + private final ReentrantLock addJarLock = new ReentrantLock(); + + public JnlpApplicationClassLoader(JarProvider jarProvider) { + this(jarProvider, new NativeLibrarySupport()); + } + + private JnlpApplicationClassLoader(JarProvider jarProvider, NativeLibrarySupport nativeLibrarySupport) { + super(new URL[0], getSystemClassLoader()); + this.jarProvider = jarProvider; + this.nativeLibrarySupport = nativeLibrarySupport; + } + + public void initializeEagerJars() { + jarProvider.loadEagerJars().forEach(this::addJar); + } + + private boolean loadMoreJars(final String name, final String reason) { + final boolean result = jarProvider.loadMoreJars(name).stream() + .peek(this::addJar) + .count() > 0; + + if (result) { + LOG.debug("loaded more jars because of {} for {}", reason, name); + } + + return result; + } + + private void addJar(LoadableJar jar) { + LOG.debug("add jar: {}", jar.jarDesc.getLocation()); + addJarLock.lock(); + try { + jar.getLocation().ifPresent(location -> { + if (!Arrays.asList(getURLs()).contains(location)) { + if (jar.containsNativeLib()) { + nativeLibrarySupport.addSearchJar(location); + } + addURL(location); + } + }); + } finally { + addJarLock.unlock(); + } + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + do { + try { + return super.findClass(name); + } catch (ClassNotFoundException ignored) { + } + } + while (loadMoreJars(name, "findClass()")); + LOG.debug("Could not find class {}", name); + throw new ClassNotFoundException(name); + } + + @Override + protected String findLibrary(final String libname) { + return nativeLibrarySupport.findLibrary(libname) + .orElseGet(() -> super.findLibrary(libname)); + } + + @Override + public URL findResource(final String name) { + do { + final URL result = super.findResource(name); + if (result != null) { + return result; + } + } + while (loadMoreJars(name, "findResource()")); + LOG.debug("Could not find resource {}", name); + return null; + } + + @Override + public Enumeration findResources(final String name) throws IOException { + return super.findResources(name); + } + + public interface JarProvider { + /** + * @return a list with all eager jars which have not yet been loaded. + */ + List loadEagerJars(); + + /** + * Loads more jars. If no more jars can be loaded then an empty list is returned. + * + * @param name the name of the class/resource which is needed by the classloader. + * @return the list of additional jars or an empty list if all jars have been loaded. + */ + List loadMoreJars(String name); + } + + public static class LoadableJar { + private final Optional location; + private final JARDesc jarDesc; + + LoadableJar(final Optional location, final JARDesc jarDesc) { + this.location = location; + this.jarDesc = jarDesc; + } + + public Optional getLocation() { + return location; + } + + public boolean containsNativeLib() { + return jarDesc.isNative(); + } + + public JARDesc getJarDesc() { + return jarDesc; + } + + @Override + public String toString() { + return new StringJoiner(", ", LoadableJar.class.getSimpleName() + "[", "]") + .add("location=" + location.map(URL::toString).orElse("--NOT FOUND IN CACHE--")) + .add("jarDesc=" + jarDesc) + .toString(); + } + + public String toLoggingString() { + final VersionString version = getJarDesc().getVersion(); + final URL location = getJarDesc().getLocation(); + return version != null ? location + "(v:" + version + ")" : location.toString(); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/NativeLibrarySupport.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/NativeLibrarySupport.java new file mode 100644 index 000000000..8ee059bd2 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/NativeLibrarySupport.java @@ -0,0 +1,131 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.JavaSystemProperties; +import net.adoptopenjdk.icedteaweb.io.FileUtils; +import net.adoptopenjdk.icedteaweb.io.IOUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_DYLIB; +import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_JNILIB; + +/** + * Handles loading and access of native code loading through a JNLP application. + * Stores native code in a temporary folder. + */ + class NativeLibrarySupport { + + private static final String NATIVE_LIB_EXT_DYLIB = ".dylib"; + private static final String NATIVE_LIB_EXT_JNILIB = ".jnilib"; + private static final String[] NATIVE_LIBRARY_EXTENSIONS = {".so", NATIVE_LIB_EXT_DYLIB, NATIVE_LIB_EXT_JNILIB, ".framework", ".dll"}; + + private final File nativeSearchDirectory; + + public NativeLibrarySupport() { + this.nativeSearchDirectory = createNativeStoreDirectory(); + } + + public Optional findLibrary(final String libname) { + final String syslib = System.mapLibraryName(libname); + final String nativeLibrary = findNativeLibrary(syslib); + // required for macOS, as of + // https://bugs.openjdk.java.net/browse/JDK-8127215 + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7158157 + if (nativeLibrary == null && syslib.endsWith(NATIVE_LIB_EXT_DYLIB)) { + return Optional.ofNullable(findNativeLibrary("lib" + libname + NATIVE_LIB_EXT_JNILIB)); + } + return Optional.ofNullable(nativeLibrary); + } + + private String findNativeLibrary(String syslib) { + final File target = new File(nativeSearchDirectory, syslib); + if (target.exists()) { + return target.getPath(); + } + return null; + } + + public void addSearchJar(final URL jarLocation) { + try { + final File localFile = Paths.get(jarLocation.toURI()).toFile(); + + try (final JarFile jarFile = new JarFile(localFile, false)) { + jarFile.stream() + .filter(entry -> !entry.isDirectory()) + .filter(entry -> isSupportedLibrary(entry.getName())) + .forEach(entry -> storeLibrary(jarFile, entry)); + } + } catch (final Exception e) { + throw new RuntimeException("Unable to inspect jar for native libraries: " + jarLocation, e); + } + } + + private synchronized void storeLibrary(final JarFile jarFile, final JarEntry entry) { + try { + final File outFile = new File(nativeSearchDirectory, entry.getName()); + if (outFile.exists()) { + throw new RuntimeException("Native file with given name " + entry.getName() + " already exists."); + } + if (!outFile.isFile()) { + FileUtils.createRestrictedFile(outFile); + outFile.deleteOnExit(); + } + try (final FileOutputStream out = new FileOutputStream(outFile)) { + IOUtils.copy(jarFile.getInputStream(entry), out); + } + } catch (final Exception e) { + throw new RuntimeException("Error while storing native library " + entry + " that is part of jar " + jarFile.getName(), e); + } + } + + private boolean isSupportedLibrary(final String filename) { + return Stream.of(NATIVE_LIBRARY_EXTENSIONS).anyMatch(filename::endsWith); + } + + /** + * Create a random base directory to cache native code files in. + * The directory has restricted access such that only the current user can access it. + * This is to reduce the chance some other user can manipulate the native libs in the cache. + */ + private static File createNativeStoreDirectory() { + final String javaTempDir = JavaSystemProperties.getJavaTempDir(); + final File parent = new File(javaTempDir); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IllegalStateException("Java temp dir '" + javaTempDir + "' is not a directory and cannot be created"); + } + + final File nativeDir = new File(parent, "itw-native-" + UUID.randomUUID().toString()); + try { + FileUtils.createRestrictedDirectory(nativeDir); + nativeDir.deleteOnExit(); + return nativeDir; + } catch (IOException e) { + throw new RuntimeException("Exception while creating native storage directory '" + nativeDir + "'", e); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Part.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Part.java new file mode 100644 index 000000000..2628ee47a --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/Part.java @@ -0,0 +1,111 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PackageDesc; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.UUID; + +/** + * ... + */ +public class Part { + + private final String name; + private boolean lazy = true; + + private final Extension extension; + + private final List jars = new ArrayList<>(); + private final List packages = new ArrayList<>(); + + Part() { + this(UUID.randomUUID().toString()); + } + + Part(final String name) { + this(null, name); + } + + Part(final Extension extension, final String name) { + this.extension = extension; + this.name = name; + } + + void addJar(final JARDesc jarDescription) { + if (!jarDescription.isLazy()) { + markAsEager(); + } + + jars.add(jarDescription); + } + + void addPackage(final PackageDesc packageDef) { + packages.add(packageDef); + } + + void markAsEager() { + lazy = false; + } + + public List getJars() { + return Collections.unmodifiableList(jars); + } + + public List getPackages() { + return Collections.unmodifiableList(packages); + } + + public boolean supports(final String resourceName) { + return packages.stream().anyMatch(partPackage -> partPackage.matches(resourceName)); + } + + public String getName() { + return name; + } + + public boolean isLazy() { + return lazy; + } + + public Extension getExtension() { + return extension; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Part part = (Part) o; + return Objects.equals(name, part.name) && + Objects.equals(extension, part.extension) && + lazy == part.lazy; + } + + @Override + public int hashCode() { + return Objects.hash(name, extension, lazy); + } + + @Override + public String toString() { + return new StringJoiner(", ", Part.class.getSimpleName() + "{", "}") + .add("name='" + name + "'") + .add("lazy=" + lazy) + .add("jars=" + jars) + .add("packages=" + packages) + .toString(); + } + + public boolean containsJar(final URL ref, final VersionString version) { + return jars.stream().anyMatch(jarDesc -> + Objects.equals(jarDesc.getLocation(), ref) && Objects.equals(jarDesc.getVersion(), version) + ); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractor.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractor.java new file mode 100644 index 000000000..1f5703ec4 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractor.java @@ -0,0 +1,157 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDownloadDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JNLPResources; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PackageDesc; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionId; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.toList; +import static net.adoptopenjdk.icedteaweb.StringUtils.isBlank; +import static net.adoptopenjdk.icedteaweb.classloader.ClassLoaderUtils.getClassloaderBackgroundExecutor; +import static net.adoptopenjdk.icedteaweb.classloader.ClassLoaderUtils.waitForCompletion; + +/** + * Extracts parts out of the JNLPFile. + * During extractions only the resources relevant for the current runtime environment (Java Version, Locale, OS, ...) + * are added to the resulting parts. + * The extractor also loads the extensions (JNLPs) and extracts the parts from them. + */ +public class PartExtractor { + + private final JNLPFileFactory jnlpFileFactory; + + private final Lock partsLock = new ReentrantLock(); + private final List parts = new ArrayList<>(); + + private final Part defaultEagerPart; + private final Part defaultLazyPart; + + public PartExtractor(final JNLPFile jnlpFile, JNLPFileFactory jnlpFileFactory) { + this.jnlpFileFactory = jnlpFileFactory; + + this.defaultEagerPart = new Part(); + parts.add(defaultEagerPart); + defaultEagerPart.markAsEager(); + + this.defaultLazyPart = new Part(); + parts.add(defaultLazyPart); + + addJnlpFile(jnlpFile, false); + } + + public List getParts() { + return unmodifiableList(parts); + } + + private void addJnlpFile(final JNLPFile jnlpFile, boolean isExtension) { + final JNLPResources resources = jnlpFile.getJnlpResources(); + + final List> extensionTasks = resources.getExtensions().stream() + .map(extension -> addExtension(jnlpFile, extension)) + .collect(toList()); + + final List> packageTasks = resources.getPackages().stream() + .map(packageDesc -> addPackage(jnlpFile, packageDesc, isExtension)) + .collect(toList()); + + final List> jarTasks = resources.getJARs().stream() + .map(jarDesc -> addJar(jnlpFile, jarDesc, isExtension)) + .collect(toList()); + + extensionTasks.forEach(f -> waitForCompletion(f, "Error while loading extensions!")); + packageTasks.forEach(f -> waitForCompletion(f, "Error while processing packages!")); + jarTasks.forEach(f -> waitForCompletion(f, "Error while processing jars!")); + } + + private Future addExtension(final JNLPFile parent, final ExtensionDesc extension) { + return CompletableFuture.runAsync(() -> { + try { + final JNLPFile jnlpFile = jnlpFileFactory.create(extension.getLocation(), extension.getVersion(), parent.getParserSettings()); + addExtensionParts(parent, jnlpFile, extension.getDownloads()); + addJnlpFile(jnlpFile, true); + } catch (Exception e) { + throw new RuntimeException("Error in adding extension " + extension.getName(), e); + } + }, getClassloaderBackgroundExecutor()); + } + + private void addExtensionParts(final JNLPFile parentFile, final JNLPFile extensionFile, final List downloads) { + partsLock.lock(); + try { + for (ExtensionDownloadDesc download : downloads) { + final String extPartName = download.getExtPart(); + final String partName = download.getPart(); + + + final Part part = isBlank(partName) ? getOrCreatePart(parentFile, extPartName, true) : getOrCreatePart(extensionFile, partName, true); + + if (!download.isLazy()) { + part.markAsEager(); + } + } + } finally { + partsLock.unlock(); + } + } + + private Part getOrCreatePart(final JNLPFile jnlpFile, final String name, final boolean isExtension) { + final URL location = jnlpFile.getSourceLocation(); + final String version = Optional.ofNullable(jnlpFile.getFileVersion()) + .map(VersionId::toString) + .orElse(null); + final Extension extension = isExtension ? new Extension(location, version) : null; + + return parts.stream() + .filter(p -> Objects.equals(p.getName(), name)) + .filter(p -> Objects.equals(p.getExtension(), extension)) + .findFirst() + .orElseGet(() -> { + final Part part = new Part(extension, name); + parts.add(part); + return part; + }); + } + + private Future addPackage(final JNLPFile jnlpFile, final PackageDesc packageDesc, final boolean isExtension) { + partsLock.lock(); + try { + final Part part = getOrCreatePart(jnlpFile, packageDesc.getPart(), isExtension); + part.addPackage(packageDesc); + } finally { + partsLock.unlock(); + } + return CompletableFuture.completedFuture(null); + } + + private Future addJar(final JNLPFile jnlpFile, final JARDesc jarDescription, final boolean isExtension) { + final String partName = jarDescription.getPart(); + + partsLock.lock(); + try { + final Part part = isBlank(partName) ? getDefaultPart(jarDescription) : getOrCreatePart(jnlpFile, partName, isExtension); + part.addJar(jarDescription); + } finally { + partsLock.unlock(); + } + return CompletableFuture.completedFuture(null); + } + + private Part getDefaultPart(final JARDesc jarDescription) { + return jarDescription.isLazy() && !jarDescription.isMain() ? defaultLazyPart : defaultEagerPart; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartsHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartsHandler.java new file mode 100644 index 000000000..6fb885833 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/classloader/PartsHandler.java @@ -0,0 +1,283 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.StringUtils; +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader.JarProvider; +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader.LoadableJar; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTrackerFactory; +import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; +import net.sourceforge.jnlp.DownloadOptions; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.services.PartsCache; + +import java.io.File; +import java.io.FileOutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import static net.adoptopenjdk.icedteaweb.classloader.ClassLoaderUtils.getClassloaderBackgroundExecutor; +import static net.adoptopenjdk.icedteaweb.classloader.ClassLoaderUtils.waitForCompletion; + +public class PartsHandler implements JarProvider, PartsCache { + + private static final Logger LOG = LoggerFactory.getLogger(PartsHandler.class); + + private final List parts; + private final Lock partsLock = new ReentrantLock(); + private final Set downloadedParts = new HashSet<>(); + private final Set loadedByClassloaderParts = new HashSet<>(); + + private final Map resourceDownloadLocks = new HashMap<>(); + + private final ResourceTracker tracker; + private final ApplicationTrustValidator trustValidator; + + /* Only used in tests. */ + protected PartsHandler(final List parts, final ApplicationTrustValidator trustValidator) { + this(parts, new DefaultResourceTrackerFactory().create(true, DownloadOptions.NONE, JNLPRuntime.getDefaultUpdatePolicy()), trustValidator); + } + + public PartsHandler(final List parts, final JNLPFile file, final ResourceTracker tracker) { + this(parts, tracker, new ApplicationTrustValidatorImpl(file)); + } + + private PartsHandler(final List parts, final ResourceTracker tracker, final ApplicationTrustValidator trustValidator) { + this.tracker = tracker; + this.parts = new CopyOnWriteArrayList<>(parts); + this.trustValidator = trustValidator; + } + + @Override + public List loadEagerJars() { + partsLock.lock(); + try { + final List eagerParts = parts.stream() + .filter(part -> !part.isLazy()) + .filter(part -> !loadedByClassloaderParts.contains(part)) + .collect(Collectors.toList()); + + return loadEagerParts(eagerParts); + } finally { + partsLock.unlock(); + } + } + + private List loadEagerParts(final List eagerParts) { + final List result = eagerParts.stream() + .map(this::downloadAllOfPart) + .flatMap(List::stream) + .collect(Collectors.toList()); + + LOG.debug("eager loaded the following jars: {}", result.stream() + .filter(jar -> jar.getLocation().isPresent()) + .map(LoadableJar::toLoggingString) + .collect(Collectors.toList()) + ); + + final String failedToLoadJars = result.stream() + .filter(jar -> !jar.getLocation().isPresent()) + .map(LoadableJar::toLoggingString) + .collect(Collectors.joining(", ")); + if (!StringUtils.isBlank(failedToLoadJars)) { + LOG.debug("failed to download the following jars: {}", failedToLoadJars); + } + + trustValidator.validateEagerJars(result); + loadedByClassloaderParts.addAll(eagerParts); + + return result; + } + + @Override + public List loadMoreJars(String resourceName) { + partsLock.lock(); + try { + final List notLoaded = parts.stream() + .filter(part -> !loadedByClassloaderParts.contains(part)) + .filter(part -> !part.getJars().isEmpty()) + .collect(Collectors.toList()); + + if (notLoaded.isEmpty()) { + LOG.info("No more parts to load."); + return Collections.emptyList(); + } + + final List supportingJars = notLoaded.stream() + .filter(part -> part.supports(resourceName)) + .findFirst() + .map(this::loadLazyPart) + .orElse(Collections.emptyList()); + + if (!supportingJars.isEmpty()) { + return supportingJars; + } + + return notLoaded.stream() + .filter(part -> part.getPackages().isEmpty()) + .findFirst() + .map(this::loadLazyPart) + .orElse(Collections.emptyList()); + } finally { + partsLock.unlock(); + } + } + + private List loadLazyPart(final Part part) { + final List result = downloadAllOfPart(part); + trustValidator.validateLazyJars(result); + loadedByClassloaderParts.add(part); + + LOG.debug("lazy loaded part {}", part.getName()); + LOG.debug("downloaded the following jars: {}", result.stream() + .filter(jar -> jar.getLocation().isPresent()) + .map(LoadableJar::toLoggingString) + .collect(Collectors.joining(", ")) + ); + + final String failedToLoadJars = result.stream() + .filter(jar -> !jar.getLocation().isPresent()) + .map(LoadableJar::toLoggingString) + .collect(Collectors.joining(", ")); + if (!StringUtils.isBlank(failedToLoadJars)) { + LOG.debug("failed to download the following jars: {}", failedToLoadJars); + } + + return result; + } + + private List downloadAllOfPart(final Part part) { + final List> tasks = part.getJars().stream() + .map(this::downloadJar) + .collect(Collectors.toList()); + + final List result = tasks.stream() + .map(future -> waitForCompletion(future, "Error while downloading jar!")) + .collect(Collectors.toList()); + + downloadedParts.add(part); + return result; + } + + private Future downloadJar(final JARDesc jarDesc) { + return CompletableFuture.supplyAsync(() -> { + final Optional localCacheUrl = getLocalUrlForJar(jarDesc); + return new LoadableJar(localCacheUrl, jarDesc); + }, getClassloaderBackgroundExecutor()); + } + + protected Optional getLocalUrlForJar(final JARDesc jarDesc) { + LOG.debug("Trying to get local URL of JAR '{}'", jarDesc.getLocation()); + print("Trying to get local URL of JAR '" + jarDesc.getLocation() + "'"); + final Lock jarLock = getOrCreateLock(jarDesc.getLocation()); + jarLock.lock(); + try { + if (!tracker.isResourceAdded(jarDesc.getLocation())) { + tracker.addResource(jarDesc.getLocation(), jarDesc.getVersion()); + } + final Optional url = Optional.ofNullable(tracker.getCacheFile(jarDesc.getLocation())) + .map(File::toURI) + .map(uri -> { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + LOG.warn("MalformedURLException for URI '{}'", uri); + return null; + } + }); + LOG.debug("Local URL of JAR '{}' is '{}'", jarDesc.getLocation(), url); + print("Local URL of JAR '" + jarDesc.getLocation() + "' is '" + url + "'"); + return url; + } catch (final Exception e) { + print("Unable to provide local URL for JAR '" + jarDesc.getLocation() + "'. Error: " + e.getMessage()); + throw new RuntimeException("Unable to provide local URL for JAR '" + jarDesc.getLocation() + "'", e); + } finally { + jarLock.unlock(); + } + } + + private synchronized Lock getOrCreateLock(final URL resourceUrl) { + return resourceDownloadLocks.computeIfAbsent(resourceUrl, url -> new ReentrantLock()); + } + + //Methods that are needed for JNLP DownloadService interface + + @Override + public void downloadPart(final String partName) { + downloadPart(partName, null); + } + + @Override + public void downloadPart(final String partName, final Extension extension) { + partsLock.lock(); + try { + parts.stream() + .filter(part -> Objects.equals(extension, part.getExtension())) + .filter(part -> Objects.equals(partName, part.getName())) + .findFirst() + .ifPresent(this::downloadAllOfPart); + } finally { + partsLock.unlock(); + } + } + + @Override + public void downloadPartContainingJar(final URL ref, final VersionString version) { + parts.stream() + .filter(part -> part.containsJar(ref, version)) + .findFirst() + .ifPresent(this::downloadAllOfPart); + } + + @Override + public boolean isPartDownloaded(final String partName) { + return isPartDownloaded(partName, null); + } + + @Override + public boolean isPartDownloaded(final String partName, final Extension extension) { + partsLock.lock(); + try { + return parts.stream() + .filter(part -> Objects.equals(extension, part.getExtension())) + .filter(part -> Objects.equals(partName, part.getName())) + .anyMatch(downloadedParts::contains); + } finally { + partsLock.unlock(); + } + } + + @Override + public boolean isPartContainingJar(final URL ref, final VersionString version) { + return parts.stream().anyMatch(part -> part.containsJar(ref, version)); + } + + //JUST FOR CURRENT TESTS! + @Deprecated + private void print(final String message) { + try (FileOutputStream out = new FileOutputStream(new File(System.getProperty("user.home") + "/Desktop/itw-log.txt"), true)) { + out.write((message + System.lineSeparator()).getBytes()); + } catch (final Exception e) { + throw new RuntimeException("Can not write message to file!", e); + } finally { + System.out.println(message); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/BasicExceptionDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/BasicExceptionDialog.java index 0449bc24d..cffff50fb 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/BasicExceptionDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/BasicExceptionDialog.java @@ -151,6 +151,7 @@ public void actionPerformed(final ActionEvent e) { errorDialog.pack(); errorDialog.setResizable(true); + errorDialog.setAlwaysOnTop(true); ScreenFinder.centerWindowsToCurrentScreen(errorDialog); errorDialog.setVisible(true); errorDialog.dispose(); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/GuiLaunchHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/GuiLaunchHandler.java index 091f14fd9..20b7ba0f5 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/GuiLaunchHandler.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/GuiLaunchHandler.java @@ -35,6 +35,7 @@ import net.adoptopenjdk.icedteaweb.client.parts.splashscreen.JNLPSplashScreen; import net.adoptopenjdk.icedteaweb.jnlp.element.information.IconKind; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTracker; import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; import net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils; import net.sourceforge.jnlp.AbstractLaunchHandler; @@ -107,7 +108,7 @@ public void launchInitialized(final JNLPFile file) { final URL splashImageURL = file.getInformation().getIconLocation( IconKind.SPLASH, preferredWidth, preferredHeight); - final ResourceTracker resourceTracker = new ResourceTracker(true); + final ResourceTracker resourceTracker = new DefaultResourceTracker(true); if (splashImageURL != null) { resourceTracker.addResource(splashImageURL, null); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/certificateviewer/CertificatePane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/certificateviewer/CertificatePane.java index bfff4f71f..45d4a3596 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/certificateviewer/CertificatePane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/certificateviewer/CertificatePane.java @@ -36,7 +36,8 @@ import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; import net.adoptopenjdk.icedteaweb.client.controlpanel.ControlPanel; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; +import net.adoptopenjdk.icedteaweb.io.FileUtils; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.sourceforge.jnlp.runtime.JNLPRuntime; @@ -44,7 +45,6 @@ import net.sourceforge.jnlp.security.KeyStores; import net.sourceforge.jnlp.security.KeyStores.Level; import net.sourceforge.jnlp.security.SecurityUtil; -import net.adoptopenjdk.icedteaweb.io.FileUtils; import javax.swing.BorderFactory; import javax.swing.JButton; @@ -560,7 +560,7 @@ public void actionPerformed(ActionEvent e) { if (selectedRow != -1 && selectedRow >= 0) { int modelIndex = table.getRowSorter().convertRowIndexToModel(selectedRow); X509Certificate c = certs.get(modelIndex); - SecurityDialog.showSingleCertInfoDialog(c, parent); + Dialogs.showSingleCertInfoDialog(c, parent); } } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/FreeDesktopIntegrationEditorFrame.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/FreeDesktopIntegrationEditorFrame.java index 2cf96ccb5..bb28aa865 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/FreeDesktopIntegrationEditorFrame.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/FreeDesktopIntegrationEditorFrame.java @@ -32,11 +32,10 @@ */ package net.adoptopenjdk.icedteaweb.client.controlpanel.desktopintegrationeditor; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; -import net.sourceforge.jnlp.config.PathsAndFiles; -import net.sourceforge.jnlp.util.XDesktopEntry; import net.adoptopenjdk.icedteaweb.client.console.ConsoleOutputPaneModel; import net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils; +import net.sourceforge.jnlp.config.PathsAndFiles; +import net.sourceforge.jnlp.util.XDesktopEntry; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -62,6 +61,7 @@ import java.util.List; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; public class FreeDesktopIntegrationEditorFrame extends JFrame { @@ -131,7 +131,7 @@ private void setTexts() { reloadsListButton.setText(R("DIMreloadLists")); selectAll.setText(R("DIMselectAll")); cleanAll.setText(R("DIMclearSelection")); - title.setText(SecurityDialogPanel.htmlWrap("

    " + R("DIMdescription") + "

    ")); + title.setText(htmlWrap("

    " + R("DIMdescription") + "

    ")); } private JPanel createMainControls() { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/Panels.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/Panels.java index edfa2d78d..1cc576c5c 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/Panels.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/controlpanel/desktopintegrationeditor/Panels.java @@ -32,8 +32,6 @@ */ package net.adoptopenjdk.icedteaweb.client.controlpanel.desktopintegrationeditor; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; - import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; @@ -44,6 +42,7 @@ import java.awt.event.ActionListener; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; public class Panels { @@ -108,7 +107,7 @@ private static String createToolTip(String tooltip, JList list) { .append(model.getFile()).append("
    " + "
  • ") .append(model.toString()).append("

  • " + "
  • ") .append(tooltip).append(""); - String tt = SecurityDialogPanel.htmlWrap(sb.toString()); + String tt = htmlWrap(sb.toString()); return tt; } return null; diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactory.java similarity index 74% rename from core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java rename to core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactory.java index 59ce9fdcc..48e7459bc 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactory.java @@ -31,8 +31,11 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ -package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; +package net.adoptopenjdk.icedteaweb.client.parts.dialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogMessage; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogMessageHandler; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.resources.Resource; @@ -44,20 +47,23 @@ import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandboxLimited; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; import net.sourceforge.jnlp.util.UrlUtils; import javax.swing.JDialog; +import java.awt.Component; import java.awt.Dialog.ModalityType; +import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.NetPermission; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.cert.X509Certificate; import java.util.Set; import java.util.concurrent.Semaphore; @@ -74,41 +80,22 @@ * {@link SecurityDialogMessageHandler} and block waiting for a response. *

    */ -public class SecurityDialogs { +public class DefaultDialogFactory implements DialogFactory { - private final static Logger LOG = LoggerFactory.getLogger(SecurityDialogs.class); - - /** - * Types of dialogs we can create - */ - public static enum DialogType { - - CERT_WARNING, - MORE_INFO, - CERT_INFO, - SINGLE_CERT_INFO, - ACCESS_WARNING, - PARTIALLY_SIGNED_WARNING, - UNSIGNED_WARNING, /* requires confirmation with 'high-security' setting */ - APPLET_WARNING, - AUTHENTICATION, - UNSIGNED_EAS_NO_PERMISSIONS_WARNING, /* when Extended applet security is at High Security and no permission attribute is find, */ - MISSING_ALACA, /*alaca - Application-Library-Allowable-Codebase Attribute*/ - MATCHING_ALACA, - SECURITY_511 - } + private static final Logger LOG = LoggerFactory.getLogger(DefaultDialogFactory.class); /** * Shows a warning dialog for different types of system access (i.e. file * open/save, clipboard read/write, printing, etc). * * @param accessType the type of system access requested. - * @param file the jnlp file associated with the requesting application. - * @param extras array of objects used as extra.toString or similarly later + * @param file the jnlp file associated with the requesting application. + * @param extras array of objects used as extra.toString or similarly later * @return true if permission was granted by the user, false otherwise. */ - public static AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, - final JNLPFile file, final Object[] extras) { + @Override + public AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, + final JNLPFile file, final Object[] extras) { final SecurityDialogMessage message = new SecurityDialogMessage(file); @@ -120,41 +107,23 @@ public static AccessWarningPaneComplexReturn showAccessWarningDialog(final Acces } - /** - * Shows a warning dialog for when a plugin applet is unsigned. This is used - * with 'high-security' setting. - * - * @param file the file to be base as information source for this dialogue - * @return true if permission was granted by the user, false otherwise. - */ - public static YesNoSandboxLimited showUnsignedWarningDialog(JNLPFile file) { - - final SecurityDialogMessage message = new SecurityDialogMessage(file); - message.dialogType = DialogType.UNSIGNED_WARNING; - message.accessType = AccessType.UNSIGNED; - - DialogResult r = getUserResponse(message); - - return (YesNoSandboxLimited) r; - } - /** * Shows a security warning dialog according to the specified type of * access. If {@code accessType} is one of {@link AccessType#VERIFIED} or * {@link AccessType#UNVERIFIED}, extra details will be available with * regards to code signing and signing certificates. * - * @param accessType the type of warning dialog to show - * @param file the JNLPFile associated with this warning - * @param certVerifier the JarCertVerifier used to verify this application + * @param accessType the type of warning dialog to show + * @param file the JNLPFile associated with this warning + * @param certVerifier the JarCertVerifier used to verify this application * @param securityDelegate the delegate for security atts. - * * @return RUN if the user accepted the certificate, SANDBOX if the user * wants the applet to run with only sandbox permissions, or CANCEL if the * user did not accept running the applet */ - public static YesNoSandbox showCertWarningDialog(AccessType accessType, - JNLPFile file, CertVerifier certVerifier, SecurityDelegate securityDelegate) { + @Override + public YesNoSandbox showCertWarningDialog(AccessType accessType, + JNLPFile file, CertVerifier certVerifier, SecurityDelegate securityDelegate) { final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.CERT_WARNING; @@ -171,13 +140,14 @@ public static YesNoSandbox showCertWarningDialog(AccessType accessType, * Shows a warning dialog for when an applet or application is partially * signed. * - * @param file the JNLPFile associated with this warning - * @param certVerifier the JarCertVerifier used to verify this application + * @param file the JNLPFile associated with this warning + * @param certVerifier the JarCertVerifier used to verify this application * @param securityDelegate the delegate for security atts. * @return true if permission was granted by the user, false otherwise. */ - public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertVerifier certVerifier, - SecurityDelegate securityDelegate) { + @Override + public YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertVerifier certVerifier, + SecurityDelegate securityDelegate) { final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.PARTIALLY_SIGNED_WARNING; @@ -194,15 +164,16 @@ public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertV * and returns the user's response. The caller must have * NetPermission("requestPasswordAuthentication") for this to work. * - * @param host The host for with authentication is needed - * @param port The port being accessed + * @param host The host for with authentication is needed + * @param port The port being accessed * @param prompt The prompt (realm) as presented by the server - * @param type The type of server (proxy/web) + * @param type The type of server (proxy/web) * @return an array of objects representing user's authentication tokens * @throws SecurityException if the caller does not have the appropriate - * permissions. + * permissions. */ - public static NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type) { + @Override + public NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type) { LOG.debug("Showing dialog for basic auth for {}:{} with name {} of type {}", host, port, prompt, type); SecurityManager sm = System.getSecurityManager(); @@ -221,7 +192,8 @@ public static NamePassword showAuthenticationPrompt(String host, int port, Strin return (NamePassword) response; } - public static boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, Set remoteUrls) { + @Override + public boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, Set remoteUrls) { SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.MISSING_ALACA; @@ -242,7 +214,8 @@ public static boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, return selectedValue.toBoolean(); } - public static boolean showMatchingALACAttributePanel(JNLPFile file, URL documentBase, Set remoteUrls) { + @Override + public boolean showMatchingALACAttributePanel(JNLPFile file, URL documentBase, Set remoteUrls) { SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.MATCHING_ALACA; @@ -263,7 +236,8 @@ public static boolean showMatchingALACAttributePanel(JNLPFile file, URL document } - public static boolean showMissingPermissionsAttributeDialogue(JNLPFile file) { + @Override + public boolean showMissingPermissionsAttributeDialogue(JNLPFile file) { SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING; @@ -283,12 +257,12 @@ public static boolean showMissingPermissionsAttributeDialogue(JNLPFile file) { * EventDispatchThread. * * @param message the SecurityDialogMessage indicating what type of dialog to - * display + * display * @return The user's response. Can be null. The exact answer depends on the * type of message, but generally an Integer corresponding to the value 0 * indicates success/proceed, and everything else indicates failure */ - private static DialogResult getUserResponse(final SecurityDialogMessage message) { + private DialogResult getUserResponse(final SecurityDialogMessage message) { /* * Want to show a security warning, while blocking the client * application. This would be easy except there is a bug in showing @@ -358,9 +332,12 @@ public Void run() { return message.userResponse; } - // false = terminate ITW - // true = continue - public static boolean show511Dialogue(Resource r) { + /** + * false = terminate ITW + * true = continue + */ + @Override + public boolean show511Dialogue(Resource r) { SecurityDialogMessage message = new SecurityDialogMessage(null); message.dialogType = DialogType.SECURITY_511; message.extras = new Object[]{r.getLocation()}; @@ -371,4 +348,54 @@ public static boolean show511Dialogue(Resource r) { return true; } + /** + * Shows more information regarding jar code signing + * + * @param certVerifier the JarCertVerifier used to verify this application + * @param file the JNLP file + */ + @Override + public void showMoreInfoDialog( + CertVerifier certVerifier, JNLPFile file) { + + SecurityDialog dialog = + new SecurityDialog(DialogType.MORE_INFO, null, file, + certVerifier); + dialog.getViewableDialog().setModalityType(ModalityType.APPLICATION_MODAL); + dialog.getViewableDialog().show(); + dialog.getViewableDialog().dispose(); + } + + /** + * Displays CertPath information in a readable table format. + * + * @param certVerifier the JarCertVerifier used to verify this application + * @param parent the parent NumberOfArguments pane + */ + @Override + public void showCertInfoDialog(CertVerifier certVerifier, + Component parent) { + SecurityDialog dialog = new SecurityDialog(DialogType.CERT_INFO, + null, null, certVerifier); + dialog.getViewableDialog().setLocationRelativeTo(parent); + dialog.getViewableDialog().setModalityType(ModalityType.APPLICATION_MODAL); + dialog.getViewableDialog().show(); + dialog.getViewableDialog().dispose(); + } + + /** + * Displays a single certificate's information. + * + * @param c the X509 certificate. + * @param parent the parent pane. + */ + @Override + public void showSingleCertInfoDialog(X509Certificate c, Window parent) { + SecurityDialog dialog = new SecurityDialog(DialogType.SINGLE_CERT_INFO, c); + dialog.getViewableDialog().setLocationRelativeTo(parent); + dialog.getViewableDialog().setModalityType(ModalityType.APPLICATION_MODAL); + dialog.getViewableDialog().show(); + dialog.getViewableDialog().dispose(); + } + } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogFactory.java new file mode 100644 index 000000000..5f1033d07 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogFactory.java @@ -0,0 +1,111 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs; + +import net.adoptopenjdk.icedteaweb.resources.Resource; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.SecurityDelegate; +import net.sourceforge.jnlp.security.AccessType; +import net.sourceforge.jnlp.security.CertVerifier; + +import java.awt.Component; +import java.awt.Window; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.Set; + +public interface DialogFactory { + + /** + * Shows a warning dialog for different types of system access (i.e. file + * open/save, clipboard read/write, printing, etc). + * + * @param accessType the type of system access requested. + * @param file the jnlp file associated with the requesting application. + * @param extras array of objects used as extra.toString or similarly later + * @return true if permission was granted by the user, false otherwise. + */ + AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, + final JNLPFile file, final Object[] extras); + + /** + * Shows a security warning dialog according to the specified type of + * access. If {@code accessType} is one of {@link AccessType#VERIFIED} or + * {@link AccessType#UNVERIFIED}, extra details will be available with + * regards to code signing and signing certificates. + * + * @param accessType the type of warning dialog to show + * @param file the JNLPFile associated with this warning + * @param certVerifier the JarCertVerifier used to verify this application + * @param securityDelegate the delegate for security atts. + * @return RUN if the user accepted the certificate, SANDBOX if the user + * wants the applet to run with only sandbox permissions, or CANCEL if the + * user did not accept running the applet + */ + YesNoSandbox showCertWarningDialog(AccessType accessType, + JNLPFile file, CertVerifier certVerifier, SecurityDelegate securityDelegate); + + /** + * Shows a warning dialog for when an applet or application is partially + * signed. + * + * @param file the JNLPFile associated with this warning + * @param certVerifier the JarCertVerifier used to verify this application + * @param securityDelegate the delegate for security atts. + * @return true if permission was granted by the user, false otherwise. + */ + YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertVerifier certVerifier, + SecurityDelegate securityDelegate); + + /** + * Present a dialog to the user asking them for authentication information, + * and returns the user's response. The caller must have + * NetPermission("requestPasswordAuthentication") for this to work. + * + * @param host The host for with authentication is needed + * @param port The port being accessed + * @param prompt The prompt (realm) as presented by the server + * @param type The type of server (proxy/web) + * @return an array of objects representing user's authentication tokens + * @throws SecurityException if the caller does not have the appropriate + * permissions. + */ + NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type); + + boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, Set remoteUrls); + + boolean showMatchingALACAttributePanel(JNLPFile file, URL documentBase, Set remoteUrls); + + boolean showMissingPermissionsAttributeDialogue(JNLPFile file); + + /** + * false = terminate ITW + * true = continue + */ + boolean show511Dialogue(Resource r); + + /** + * Shows more information regarding jar code signing + * + * @param certVerifier the JarCertVerifier used to verify this application + * @param file the JNLP file + */ + void showMoreInfoDialog(CertVerifier certVerifier, JNLPFile file); + + /** + * Displays CertPath information in a readable table format. + * + * @param certVerifier the JarCertVerifier used to verify this application + * @param parent the parent NumberOfArguments pane + */ + void showCertInfoDialog(CertVerifier certVerifier, Component parent); + + /** + * Displays a single certificate's information. + * + * @param c the X509 certificate. + * @param parent the parent pane. + */ + void showSingleCertInfoDialog(X509Certificate c, Window parent); +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogType.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogType.java new file mode 100644 index 000000000..1dec8af99 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DialogType.java @@ -0,0 +1,20 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs; + +/** + * Types of dialogs we can create + */ +public enum DialogType { + CERT_WARNING, + MORE_INFO, + CERT_INFO, + SINGLE_CERT_INFO, + ACCESS_WARNING, + PARTIALLY_SIGNED_WARNING, + UNSIGNED_WARNING, /* requires confirmation with 'high-security' setting */ + APPLET_WARNING, + AUTHENTICATION, + UNSIGNED_EAS_NO_PERMISSIONS_WARNING, /* when Extended applet security is at High Security and no permission attribute is find, */ + MISSING_ALACA, /*alaca - Application-Library-Allowable-Codebase Attribute*/ + MATCHING_ALACA, + SECURITY_511 +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/Dialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/Dialogs.java new file mode 100644 index 000000000..caf6635c7 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/Dialogs.java @@ -0,0 +1,176 @@ +/* SecurityDialogs.java + Copyright (C) 2010 Red Hat, Inc. + + This file is part of IcedTea. + + IcedTea is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2. + + IcedTea 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 for more details. + + You should have received a copy of the GNU General Public License + along with IcedTea; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. + */ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogMessageHandler; +import net.adoptopenjdk.icedteaweb.resources.Resource; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.SecurityDelegate; +import net.sourceforge.jnlp.security.AccessType; +import net.sourceforge.jnlp.security.CertVerifier; + +import java.awt.Component; +import java.awt.Window; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.Set; + +/** + *

    + * A factory for showing many possible types of security warning to the user. + *

    + *

    + * This contains all the public methods that classes outside this package should + * use instead of using {@link SecurityDialog} directly. + *

    + *

    + * All of these methods post a message to the + * {@link SecurityDialogMessageHandler} and block waiting for a response. + *

    + */ +public class Dialogs { + + private static final DialogFactory DEFAULT_DIALOG_FACTORY = new DefaultDialogFactory(); + + private static DialogFactory dialogFactory = DEFAULT_DIALOG_FACTORY; + + @FunctionalInterface + public interface Uninstaller extends AutoCloseable { + void uninstall(); + + @Override + default void close() { + uninstall(); + } + } + + public static synchronized Uninstaller setDialogFactory(final DialogFactory dialogs) { + Assert.requireNonNull(dialogs, "dialogs"); + dialogFactory = dialogs; + return () -> dialogFactory = DEFAULT_DIALOG_FACTORY; + } + + private static DialogFactory getDialogs() { + return dialogFactory; + } + + /** + * see {@link DialogFactory#showAccessWarningDialog(AccessType, JNLPFile, Object[])}. + */ + public static AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, + final JNLPFile file, final Object[] extras) { + return getDialogs().showAccessWarningDialog(accessType, file, extras); + } + + /** + * see {@link DialogFactory#showCertWarningDialog(AccessType, JNLPFile, CertVerifier, SecurityDelegate)}. + */ + public static YesNoSandbox showCertWarningDialog(AccessType accessType, + JNLPFile file, CertVerifier certVerifier, SecurityDelegate securityDelegate) { + return getDialogs().showCertWarningDialog(accessType, file, certVerifier, securityDelegate); + } + + /** + * see {@link DialogFactory#showPartiallySignedWarningDialog(JNLPFile, CertVerifier, SecurityDelegate)}. + */ + public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertVerifier certVerifier, + SecurityDelegate securityDelegate) { + return getDialogs().showPartiallySignedWarningDialog(file, certVerifier, securityDelegate); + } + + /** + * see {@link DialogFactory#showAuthenticationPrompt(String, int, String, String)}. + */ + public static NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type) { + return getDialogs().showAuthenticationPrompt(host, port, prompt, type); + } + + /** + * see {@link DialogFactory#showMissingALACAttributePanel(JNLPFile, URL, Set)} + */ + public static boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, Set remoteUrls) { + return getDialogs().showMissingALACAttributePanel(file, codeBase, remoteUrls); + } + + /** + * see {@link DialogFactory#showMatchingALACAttributePanel(JNLPFile, URL, Set)}. + */ + public static boolean showMatchingALACAttributePanel(JNLPFile file, URL documentBase, Set remoteUrls) { + return getDialogs().showMatchingALACAttributePanel(file, documentBase, remoteUrls); + } + + /** + * see {@link DialogFactory#showMissingPermissionsAttributeDialogue(JNLPFile)}. + */ + public static boolean showMissingPermissionsAttributeDialogue(JNLPFile file) { + return getDialogs().showMissingPermissionsAttributeDialogue(file); + } + + /** + * see {@link DialogFactory#show511Dialogue(Resource)}. + */ + public static boolean show511Dialogue(Resource r) { + return getDialogs().show511Dialogue(r); + } + + /** + * see {@link DialogFactory#showMoreInfoDialog(CertVerifier, JNLPFile)}. + */ + public static void showMoreInfoDialog(CertVerifier certVerifier, JNLPFile file) { + getDialogs().showMoreInfoDialog(certVerifier, file); + } + + /** + * see {@link DialogFactory#showCertInfoDialog(CertVerifier, Component)}. + */ + public static void showCertInfoDialog(CertVerifier certVerifier, Component parent) { + getDialogs().showCertInfoDialog(certVerifier, parent); + } + + /** + * see {@link DialogFactory#showSingleCertInfoDialog(X509Certificate, Window)}. + */ + public static void showSingleCertInfoDialog(X509Certificate c, Window parent) { + getDialogs().showSingleCertInfoDialog(c, parent); + } + +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/readme.txt b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/readme.txt new file mode 100644 index 000000000..216d598c8 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/readme.txt @@ -0,0 +1,2 @@ +Dialog screenshots of the old dialogs as reference for development created by DefaultDialogFactory. +This directory could be removed when the NewDialogFactory is finished. \ No newline at end of file diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog1.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog1.png new file mode 100644 index 000000000..9329d1149 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog1.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog2.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog2.png new file mode 100644 index 000000000..b2237d79c Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showAccessWarningDialog2.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog.png new file mode 100644 index 000000000..82bff5b30 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog_HttpsCertVerifier.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog_HttpsCertVerifier.png new file mode 100644 index 000000000..9efec19f7 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showCertWarningDialog_HttpsCertVerifier.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMissingPermissionsAttributeDialogue.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMissingPermissionsAttributeDialogue.png new file mode 100644 index 000000000..23e03d433 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMissingPermissionsAttributeDialogue.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMoreInfoDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMoreInfoDialog.png new file mode 100644 index 000000000..b8dbe1ba5 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showMoreInfoDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showPartiallySignedWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showPartiallySignedWarningDialog.png new file mode 100644 index 000000000..4cf1b7bb2 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showPartiallySignedWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showUnsignedWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showUnsignedWarningDialog.png new file mode 100644 index 000000000..593f38b69 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/_images/showUnsignedWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AccessWarningPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AccessWarningPane.java index 14019fb4c..0e7037c2a 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AccessWarningPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AccessWarningPane.java @@ -37,19 +37,19 @@ import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.RememberPanelResult; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.RememberableDialog; import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.io.FileUtils; import net.adoptopenjdk.icedteaweb.jdk89access.SunMiscLauncher; import net.adoptopenjdk.icedteaweb.jnlp.element.information.ShortcutDesc; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNo; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.config.ConfigurationConstants; import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; -import net.adoptopenjdk.icedteaweb.io.FileUtils; -import net.sourceforge.jnlp.util.XDesktopEntry; import net.sourceforge.jnlp.util.docprovider.formatters.formatters.PlainTextFormatter; import javax.swing.BorderFactory; @@ -58,7 +58,6 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; @@ -76,6 +75,7 @@ import java.awt.event.ActionListener; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; /** * Provides a panel to show inside a SecurityDialog. These dialogs are @@ -84,21 +84,17 @@ * printer, etc) is needed with unsigned code. * * @author Joshua Sumali + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class AccessWarningPane extends SecurityDialogPanel implements RememberableDialog{ private Object[] extras; private JCheckBox desktopCheck; private JCheckBox menuCheck; - HtmlShortcutPanel htmlPanelDesktop; - HtmlShortcutPanel htmlPanelMenu; RememberPanel rememberPanel; - public AccessWarningPane(SecurityDialog x, CertVerifier certVerifier) { - super(x, certVerifier); - addComponents(); - } - public AccessWarningPane(SecurityDialog x, Object[] extras, CertVerifier certVerifier) { super(x, certVerifier); this.extras = extras; @@ -198,8 +194,8 @@ private void addComponents() { fromLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - final JButton run = new JButton(R("ButOk")); - final JButton cancel = new JButton(R("ButCancel")); + final JButton okButton = new JButton(R("ButOk")); + final JButton cancelButton = new JButton(R("ButCancel")); JPanel infoPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); @@ -255,15 +251,13 @@ private void addComponents() { //run and cancel buttons JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - JButton showAdvanced = new JButton(R("ButAdvancedOptions")); - showAdvanced.addActionListener(new ActionListener() { + JButton showAdvancedButton = new JButton(R("ButAdvancedOptions")); + showAdvancedButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { negateVisibility(rememberPanel); - negateVisibility(htmlPanelDesktop); - negateVisibility(htmlPanelMenu); - AccessWarningPane.this.parent.getViwableDialog().pack(); + AccessWarningPane.this.parent.getViewableDialog().pack(); } @@ -274,26 +268,18 @@ private void negateVisibility(JComponent a) { } } ); - run.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - parent.setValue(getModifier(Primitive.YES)); - parent.getViwableDialog().dispose(); - } + okButton.addActionListener(e -> { + parent.setValue(getModifier(Primitive.YES)); + parent.getViewableDialog().dispose(); }); - cancel.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - parent.setValue(getModifier(Primitive.NO)); - parent.getViwableDialog().dispose(); - } + cancelButton.addActionListener(e -> { + parent.setValue(getModifier(Primitive.NO)); + parent.getViewableDialog().dispose(); }); - initialFocusComponent = cancel; - buttonPanel.add(run); - buttonPanel.add(cancel); - buttonPanel.add(showAdvanced); + initialFocusComponent = cancelButton; + buttonPanel.add(okButton); + buttonPanel.add(cancelButton); + buttonPanel.add(showAdvancedButton); buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //all of the above @@ -303,34 +289,22 @@ public void actionPerformed(ActionEvent e) { add(buttonPanel); rememberPanel.setVisible(false); - this.parent.getViwableDialog().pack(); + this.parent.getViewableDialog().pack(); } private AccessWarningPaneComplexReturn getModifier(Primitive button) { AccessWarningPaneComplexReturn ar = new AccessWarningPaneComplexReturn(button); if (desktopCheck != null) { - if (htmlPanelDesktop != null) { - //html - ar.setDesktop(htmlPanelDesktop.getShortcutResult(desktopCheck.isSelected())); - } else { - //jnlp - ar.setDesktop(new AccessWarningPaneComplexReturn.ShortcutResult(desktopCheck.isSelected())); - } + ar.setDesktop(new ShortcutResult(desktopCheck.isSelected())); } if (menuCheck != null) { - if (htmlPanelMenu != null) { - //html - ar.setMenu(htmlPanelMenu.getShortcutResult(menuCheck.isSelected())); - } else { - //jnlp - ar.setMenu(new AccessWarningPaneComplexReturn.ShortcutResult(menuCheck.isSelected())); - } + ar.setMenu(new ShortcutResult(menuCheck.isSelected())); } return ar; } - private class RememberPanel extends JPanel { + private static class RememberPanel extends JPanel { final JRadioButton byApp = new JRadioButton(R("EXAWrememberByApp")); final JRadioButton byPage = new JRadioButton(R("EXAWrememberByPage")); @@ -350,7 +324,6 @@ public RememberPanel() { bg.add(byPage); bg.add(dont); this.validate(); - } private boolean isRemembered(){ @@ -361,108 +334,11 @@ private boolean isRememberedForCodebase(){ } private RememberPanelResult getResult() { - return new RememberPanelResult(isRemembered(), isRememberedForCodebase()); + return new RememberPanelResult(isRemembered(), isRememberedForCodebase()); } - } - private class HtmlShortcutPanel extends JPanel { - - final JRadioButton browser = new JRadioButton(R("EXAWbrowser"), true); - final JComboBox browsers = new JComboBox<>(XDesktopEntry.BROWSERS); - final JRadioButton jnlpGen = new JRadioButton(R("EXAWgenjnlp")); - final JRadioButton jnlpHref = new JRadioButton(R("EXAWjnlphref")); - final JRadioButton javawsHtml = new JRadioButton(R("EXAWhtml")); - final JCheckBox fix = new JCheckBox(R("EXAWfixhref")); - final ActionListener modifySecondaryControls = new ActionListener() { - - @Override - public void actionPerformed(ActionEvent ae) { - if (browser.isSelected()) { - browsers.setEnabled(true); - } else { - browsers.setEnabled(false); - } - if (jnlpHref.isSelected()) { - fix.setEnabled(true); - fix.setSelected(true); - } else { - fix.setEnabled(false); - fix.setSelected(false); - } - } - }; - - public HtmlShortcutPanel() { - super(new FlowLayout(FlowLayout.CENTER, 1, 5)); - this.setBorder(new EmptyBorder(0, 0, 0, 0)); - addMainComponents(); - setTooltips(); - ButtonGroup bg = createRadiosGroup(); - // init checkbox - modifySecondaryControls.actionPerformed(null); - this.validate(); - - } - - public AccessWarningPaneComplexReturn.ShortcutResult getShortcutResult(boolean mainResolution) { - AccessWarningPaneComplexReturn.ShortcutResult r = new AccessWarningPaneComplexReturn.ShortcutResult(mainResolution); - r.setBrowser((String) browsers.getSelectedItem()); - r.setFixHref(fix.isSelected()); - if (browser.isSelected()) { - r.setShortcutType(AccessWarningPaneComplexReturn.Shortcut.BROWSER); - } else if (jnlpGen.isSelected()) { - r.setShortcutType(AccessWarningPaneComplexReturn.Shortcut.GENERATED_JNLP); - } else if (jnlpHref.isSelected()) { - r.setShortcutType(AccessWarningPaneComplexReturn.Shortcut.JNLP_HREF); - } else if (javawsHtml.isSelected()) { - r.setShortcutType(AccessWarningPaneComplexReturn.Shortcut.JAVAWS_HTML); - } - return r; - } - - private ButtonGroup createRadiosGroup() { - ButtonGroup bg = new ButtonGroup(); - bg.add(browser); - bg.add(jnlpGen); - bg.add(jnlpHref); - bg.add(javawsHtml); - setCheckboxModifierListener(); - return bg; - } - - private void setCheckboxModifierListener() { - browser.addActionListener(modifySecondaryControls); - jnlpGen.addActionListener(modifySecondaryControls); - jnlpHref.addActionListener(modifySecondaryControls); - javawsHtml.addActionListener(modifySecondaryControls); - } - - private void addMainComponents() { - this.add(browser); - browsers.setEditable(true); - browsers.setSelectedItem(XDesktopEntry.getBrowserBin()); - this.add(browsers); - this.add(jnlpGen); - this.add(jnlpHref); - this.add(javawsHtml); - this.add(fix); - jnlpHref.setEnabled(false); - } - - private void setTooltips() { - browser.setToolTipText(R("EXAWbrowserTolltip")); - browsers.setToolTipText(R("EXAWbrowsersTolltip")); - jnlpGen.setToolTipText(R("EXAWgeneratedTolltip")); - jnlpHref.setToolTipText(R("EXAWhrefTolltip")); - javawsHtml.setToolTipText(R("EXAWhtmlTolltip")); - fix.setToolTipText(R("EXAWfixTolltip")); - } - - } - - - @Override + @Override public RememberPanelResult getRememberAction() { return rememberPanel.getResult(); } @@ -508,5 +384,4 @@ public String helpToStdIn() { return YesNo.yes().getAllowedValues().toString(); } } - } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AppletWarningPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AppletWarningPane.java index fbac6e485..4f8be770b 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AppletWarningPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/AppletWarningPane.java @@ -51,6 +51,8 @@ import java.awt.FlowLayout; import java.awt.Font; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + public class AppletWarningPane extends SecurityDialogPanel { public AppletWarningPane(SecurityDialog x, CertVerifier certVerifier) { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertWarningPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertWarningPane.java index b6a96a657..184375558 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertWarningPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertWarningPane.java @@ -34,23 +34,19 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.jdk89access.SunMiscLauncher; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; import net.sourceforge.jnlp.security.CertificateUtils; import net.sourceforge.jnlp.security.HttpsCertVerifier; -import net.sourceforge.jnlp.security.KeyStores; -import net.sourceforge.jnlp.security.KeyStores.Level; -import net.sourceforge.jnlp.security.KeyStores.Type; import net.sourceforge.jnlp.security.SecurityUtil; -import net.adoptopenjdk.icedteaweb.io.FileUtils; import javax.swing.BorderFactory; import javax.swing.BoxLayout; @@ -68,12 +64,11 @@ import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.io.File; -import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; /** * Provides the panel for using inside a SecurityDialog. These dialogs are @@ -82,7 +77,10 @@ * printer, etc) is needed with unsigned code. * * @author Joshua Sumali + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class CertWarningPane extends SecurityDialogPanel { private final static Logger LOG = LoggerFactory.getLogger(CertWarningPane.class); @@ -299,8 +297,8 @@ private void addButtons() { private class MoreInfoButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - SecurityDialog.showMoreInfoDialog(parent.getCertVerifier(), - parent); + Dialogs.showMoreInfoDialog(parent.getCertVerifier(), + parent.getFile()); } } @@ -327,26 +325,8 @@ private class CheckBoxListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (alwaysTrust != null && alwaysTrust.isSelected()) { - saveCert(); - } - } - } - - public void saveCert() { - try { - KeyStore ks = KeyStores.getKeyStore(Level.USER, Type.CERTS).getKs(); - X509Certificate c = (X509Certificate) parent.getCertVerifier().getPublisher(null); - CertificateUtils.addToKeyStore(c, ks); - File keyStoreFile = KeyStores.getKeyStoreLocation(Level.USER, Type.CERTS).getFile(); - if (!keyStoreFile.isFile()) { - FileUtils.createRestrictedFile(keyStoreFile); + CertificateUtils.saveCertificate((X509Certificate) parent.getCertVerifier().getPublisher(null)); } - SecurityUtil.storeKeyStore(ks, keyStoreFile); - LOG.debug("certificate is now permanently trusted"); - } catch (Exception ex) { - // TODO: Let NetX show a dialog here notifying user - // about being unable to add cert to keystore - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, ex); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertsInfoPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertsInfoPane.java index 31aa89773..4cf819a79 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertsInfoPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/CertsInfoPane.java @@ -84,7 +84,10 @@ * X509Certificate(s) used in jar signing. * * @author Joshua Sumali + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class CertsInfoPane extends SecurityDialogPanel { private final static Logger LOG = LoggerFactory.getLogger(CertsInfoPane.class); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/InetSecurity511Panel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/InetSecurity511Panel.java index 013aee277..72b8f85d4 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/InetSecurity511Panel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/InetSecurity511Panel.java @@ -59,6 +59,8 @@ import java.net.URL; import java.util.List; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + public class InetSecurity511Panel extends SecurityDialogPanel { private final static Logger LOG = LoggerFactory.getLogger(InetSecurity511Panel.class); @@ -87,7 +89,7 @@ public InetSecurity511Panel(final SecurityDialog sd, final URL url) { public void actionPerformed(ActionEvent e) { if (sd != null) { sd.setValue(YesCancelSkip.yes()); - parent.getViwableDialog().dispose(); + parent.getViewableDialog().dispose(); } } }); @@ -98,7 +100,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { if (sd != null) { sd.setValue(YesCancelSkip.cancel()); - parent.getViwableDialog().dispose(); + parent.getViewableDialog().dispose(); } } }); @@ -138,7 +140,7 @@ public void mouseClicked(MouseEvent e) { this.add(title, BorderLayout.NORTH); if (sd != null) { //for testing purposes - sd.getViwableDialog().pack(); + sd.getViewableDialog().pack(); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingALACAttributePanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingALACAttributePanel.java index dfa07f058..0474a195a 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingALACAttributePanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingALACAttributePanel.java @@ -43,7 +43,6 @@ import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNo; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.util.UrlUtils; import javax.imageio.ImageIO; import javax.swing.BorderFactory; @@ -51,7 +50,6 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JEditorPane; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; @@ -65,15 +63,17 @@ import java.awt.Font; import java.awt.Image; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.util.HashSet; -import java.util.Set; + +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class MissingALACAttributePanel extends SecurityDialogPanel implements RememberableDialog{ private final static Logger LOG = LoggerFactory.getLogger(MissingALACAttributePanel.class); @@ -88,7 +88,7 @@ public MissingALACAttributePanel(SecurityDialog x, String title, String codebase throw new RuntimeException(ex); } if (x != null) { - x.getViwableDialog().setMinimumSize(new Dimension(600, 400)); + x.getViewableDialog().setMinimumSize(new Dimension(600, 400)); } } @@ -151,19 +151,6 @@ public void hyperlinkUpdate(HyperlinkEvent e) { add(rememberPanel); } - - public static void main(String[] args) throws MalformedURLException { - Set s = new HashSet<>(); - s.add(new URL("http:/blah.com/blah")); - s.add(new URL("http:/blah.com/blah/blah")); - MissingALACAttributePanel w = new MissingALACAttributePanel(null, "HelloWorld", "http://nbblah.url", UrlUtils.setOfUrlsToHtmlList(s)); - JFrame f = new JFrame(); - f.setSize(600, 400); - f.add(w, BorderLayout.CENTER); - f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - f.setVisible(true); - } - @Override public RememberPanelResult getRememberAction() { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingPermissionsAttributePanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingPermissionsAttributePanel.java index 112287053..621c938b7 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingPermissionsAttributePanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MissingPermissionsAttributePanel.java @@ -50,7 +50,6 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JEditorPane; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; @@ -67,6 +66,12 @@ import java.net.URISyntaxException; import java.net.URL; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + +/** + * @deprecated will be replaced by new security dialogs + */ +@Deprecated public class MissingPermissionsAttributePanel extends SecurityDialogPanel implements RememberableDialog{ private final static Logger LOG = LoggerFactory.getLogger(MissingPermissionsAttributePanel.class); @@ -81,7 +86,7 @@ public MissingPermissionsAttributePanel(SecurityDialog x, String title, String c throw new RuntimeException(ex); } if (x != null) { - x.getViwableDialog().setMinimumSize(new Dimension(400, 400)); + x.getViewableDialog().setMinimumSize(new Dimension(400, 400)); } } @@ -147,15 +152,6 @@ public void hyperlinkUpdate(HyperlinkEvent e) { } - public static void main(String[] args) { - MissingPermissionsAttributePanel w = new MissingPermissionsAttributePanel(null, "HelloWorld", "http://nbblah.url"); - JFrame f = new JFrame(); - f.setSize(400, 400); - f.add(w, BorderLayout.CENTER); - f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - f.setVisible(true); - } - @Override public RememberPanelResult getRememberAction() { return rememberPanel.getRememberAction(); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MoreInfoPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MoreInfoPane.java index 89533b4f3..fd52d474e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MoreInfoPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/MoreInfoPane.java @@ -34,10 +34,11 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.jdk89access.SunMiscLauncher; -import net.sourceforge.jnlp.security.CertVerifier; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Yes; +import net.sourceforge.jnlp.security.CertVerifier; import javax.swing.BorderFactory; import javax.swing.ImageIcon; @@ -53,13 +54,17 @@ import java.util.List; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; /** * Provides the panel for the More Info dialog. This dialog shows details about an * application's signing status. * * @author Joshua Sumali + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class MoreInfoPane extends SecurityDialogPanel { private final boolean showSignedJNLPWarning; @@ -119,7 +124,7 @@ private void addComponents() { private class CertInfoButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - SecurityDialog.showCertInfoDialog(parent.getCertVerifier(), + Dialogs.showCertInfoDialog(parent.getCertVerifier(), parent.getSecurityDialogPanel()); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/PasswordAuthenticationPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/PasswordAuthenticationPane.java index 3cc7ba4c5..80f65b50e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/PasswordAuthenticationPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/PasswordAuthenticationPane.java @@ -38,11 +38,9 @@ import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; import javax.swing.JButton; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JTextField; -import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -54,8 +52,9 @@ /** * Modal non-minimizable dialog to request http authentication credentials + * @deprecated will be replaced by new security dialogs */ - +@Deprecated public class PasswordAuthenticationPane extends SecurityDialogPanel { private final JTextField jtfUserName = new JTextField(); @@ -74,7 +73,7 @@ public PasswordAuthenticationPane(SecurityDialog parent, Object[] extras) { type = (String) extras[3]; addComponents(); - } + } /** * Initialized the dialog components @@ -83,12 +82,12 @@ public PasswordAuthenticationPane(SecurityDialog parent, Object[] extras) { public final void addComponents() { JLabel jlInfo = new JLabel(""); - jlInfo.setText("" + R("SAuthenticationPrompt", type, host, prompt) + ""); + jlInfo.setText("" + R("SAuthenticationPrompt", type, host, prompt) + ""); setLayout(new GridBagLayout()); - JLabel jlUserName = new JLabel(R("Username")); - JLabel jlPassword = new JLabel(R("Password")); + JLabel jlUserName = new JLabel(R("Username") + ":"); + JLabel jlPassword = new JLabel(R("Password") + ":"); JButton jbOK = new JButton(R("ButOk")); JButton jbCancel = new JButton(R("ButCancel")); @@ -153,8 +152,8 @@ public final void addComponents() { setMaximumSize(new Dimension(1024, 150)); setSize(400, 150); - if (parent!=null){ - parent.getViwableDialog().setLocationRelativeTo(null); + if (parent != null) { + parent.getViewableDialog().setLocationRelativeTo(null); } initialFocusComponent = jtfUserName; @@ -162,7 +161,7 @@ public final void addComponents() { @Override public void actionPerformed(ActionEvent e) { parent.setValue(new NamePassword(jtfUserName.getText(), jpfPassword.getPassword())); - parent.getViwableDialog().dispose(); + parent.getViewableDialog().dispose(); } }; @@ -170,7 +169,7 @@ public void actionPerformed(ActionEvent e) { @Override public void actionPerformed(ActionEvent e) { parent.setValue(null); - parent.getViwableDialog().dispose(); + parent.getViewableDialog().dispose(); } }; @@ -204,15 +203,4 @@ public DialogResult readFromStdIn(String what) { public String helpToStdIn() { return Translator.R("PAPstdinInfo"); } - - - public static void main(String[] args) { - PasswordAuthenticationPane w = new PasswordAuthenticationPane(null, new Object[]{"host",666,"prompt","type"}); - JFrame f = new JFrame(); - f.setSize(400, 200); - f.add(w, BorderLayout.CENTER); - f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - f.setVisible(true); - } - } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java index 1955314b7..621ad7303 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java @@ -34,21 +34,19 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.DialogType; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.apptrustwarningpanel.AppTrustWarningDialog; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; import javax.swing.JDialog; import java.awt.BorderLayout; -import java.awt.Component; import java.awt.Dialog.ModalityType; -import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.URL; @@ -58,180 +56,96 @@ * Provides methods for showing security warning dialogs for a wide range of * JNLP security issues. Note that the security dialogs should be running in the * secure AppContext - this class should not be used directly from an applet or - * application. See {@link SecurityDialogs} for a way to show security dialogs. + * application. See {@link Dialogs} for a way to show security dialogs. * * @author Joshua Sumali */ public class SecurityDialog { - private final static Logger LOG = LoggerFactory.getLogger(SecurityDialog.class); - - /** The type of dialog we want to show */ - private final SecurityDialogs.DialogType dialogType; + /** + * The type of dialog we want to show + */ + private final DialogType dialogType; - /** The type of access that this dialog is for */ + /** + * The type of access that this dialog is for + */ private final AccessType accessType; private SecurityDialogPanel panel; - /** The application file associated with this security warning */ + /** + * The application file associated with this security warning + */ private final JNLPFile file; private final CertVerifier certVerifier; private final X509Certificate cert; - /** An optional String array that's only necessary when a dialog + /** + * An optional String array that's only necessary when a dialog * label requires some parameters (e.g. showing which address an application * is trying to connect to). */ private final Object[] extras; - /** Whether or not this object has been fully initialized */ - private boolean initialized = false; - private DialogResult value; - - private ViwableDialog viwableDialog; - /** Should show signed JNLP file warning */ - private boolean requiresSignedJNLPWarning; - - SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { - this.viwableDialog = new ViwableDialog(); - this.dialogType = dialogType; - this.accessType = accessType; - this.file = file; - this.certVerifier = JarCertVerifier; - this.cert = cert; - this.extras = extras; - initialized = true; - - if(file != null) - requiresSignedJNLPWarning= file.requiresSignedJNLPWarning(); - - initDialog(); - } + private final ViewableDialog viewableDialog; /** - * Construct a SecurityDialog to display some sort of access warning + * Should show signed JNLP file warning */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file) { - this(dialogType, accessType, file, null, null, null); - } + private boolean requiresSignedJNLPWarning; /** * Create a SecurityDialog to display a certificate-related warning */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, CertVerifier certVerifier) { + public SecurityDialog(DialogType dialogType, AccessType accessType, + JNLPFile file, CertVerifier certVerifier) { this(dialogType, accessType, file, certVerifier, null, null); } - /** - * Create a SecurityDialog to display a certificate-related warning - */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - CertVerifier certVerifier) { - this(dialogType, accessType, null, certVerifier, null, null); - } - - /** - * Create a SecurityDialog to display some sort of access warning - * with more information - */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, Object[] extras) { - this(dialogType, accessType, file, null, null, extras); - } - /** * Create a SecurityWarningDialog to display information about a single * certificate */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, X509Certificate c) { + public SecurityDialog(DialogType dialogType, X509Certificate c) { this(dialogType, null, null, null, c, null); } - /** - * Returns if this dialog has been fully initialized yet. - * @return true if this dialog has been initialized, and false otherwise. - */ - public boolean isInitialized() { - return initialized; - } - - /** - * Shows more information regarding jar code signing - * - * @param certVerifier the JarCertVerifier used to verify this application - * @param parent the parent NumberOfArguments pane - */ - public static void showMoreInfoDialog( - CertVerifier certVerifier, SecurityDialog parent) { - - JNLPFile file= parent.getFile(); - SecurityDialog dialog = - new SecurityDialog(SecurityDialogs.DialogType.MORE_INFO, null, file, - certVerifier); - dialog.getViwableDialog().setModalityType(ModalityType.APPLICATION_MODAL); - dialog.getViwableDialog().show(); - dialog.getViwableDialog().dispose(); - } - - /** - * Displays CertPath information in a readable table format. - * - * @param certVerifier the JarCertVerifier used to verify this application - * @param parent the parent NumberOfArguments pane - */ - public static void showCertInfoDialog(CertVerifier certVerifier, - Component parent) { - SecurityDialog dialog = new SecurityDialog(SecurityDialogs.DialogType.CERT_INFO, - null, null, certVerifier); - dialog.getViwableDialog().setLocationRelativeTo(parent); - dialog.getViwableDialog().setModalityType(ModalityType.APPLICATION_MODAL); - dialog.getViwableDialog().show(); - dialog.getViwableDialog().dispose(); - } + SecurityDialog(DialogType dialogType, AccessType accessType, + JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { + this.viewableDialog = new ViewableDialog(); + this.dialogType = dialogType; + this.accessType = accessType; + this.file = file; + this.certVerifier = JarCertVerifier; + this.cert = cert; + this.extras = extras; - /** - * Displays a single certificate's information. - * - * @param c the X509 certificate. - * @param parent the parent pane. - */ - public static void showSingleCertInfoDialog(X509Certificate c, - Window parent) { - SecurityDialog dialog = new SecurityDialog(SecurityDialogs.DialogType.SINGLE_CERT_INFO, c); - dialog.getViwableDialog().setLocationRelativeTo(parent); - dialog.getViwableDialog().setModalityType(ModalityType.APPLICATION_MODAL); - dialog.getViwableDialog().show(); - dialog.getViwableDialog().dispose(); - } + if (file != null) { + requiresSignedJNLPWarning = file.requiresSignedJNLPWarning(); + } - private void initDialog() { - String dialogTitle = createTitle(); + String dialogTitle = createTitle(dialogType, accessType); - // Note: ViwableDialog methods are deferred until show(): - getViwableDialog().setTitle(dialogTitle); - getViwableDialog().setModalityType(ModalityType.MODELESS); + // Note: ViewableDialog methods are deferred until show(): + getViewableDialog().setTitle(dialogTitle); + getViewableDialog().setModalityType(ModalityType.MODELESS); - getViwableDialog().setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + getViewableDialog().setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - // Initialize panel now as its constructor may call getViwableDialog() deferred methods - // to modify dialog state: - SwingUtils.invokeAndWait(new Runnable() { - @Override - public void run() { - installPanel(); - } + // Initialize panel now as its constructor may call getViewableDialog() deferred methods + // to modify dialog state: + SwingUtils.invokeAndWait(() -> { + panel = getPanel(SecurityDialog.this); + getViewableDialog().add(panel, BorderLayout.CENTER); }); - getViwableDialog().pack(); - getViwableDialog().centerDialog(); + getViewableDialog().pack(); + getViewableDialog().centerDialog(); WindowAdapter adapter = new WindowAdapter() { private boolean gotFocus = false; @@ -240,50 +154,28 @@ public void run() { public void windowGainedFocus(WindowEvent we) { // Once window gets focus, set initial focus if (!gotFocus) { - selectDefaultButton(); + if (panel != null) { + panel.requestFocusOnDefaultButton(); + } gotFocus = true; } } @Override public void windowOpened(WindowEvent e) { - getViwableDialog().setResizable(true); + getViewableDialog().setResizable(true); SecurityDialog.this.setValue(null); } + @Override public void windowClosed(WindowEvent e) { // called if the user closes the window directly (dispose on close) // always dispose() to unlock message processing - getViwableDialog().dispose(); + getViewableDialog().dispose(); } }; - getViwableDialog().addWindowListener(adapter); - getViwableDialog().addWindowFocusListener(adapter); - } - - private String createTitle() { - return createTitle(dialogType, accessType); - } - private static String createTitle(SecurityDialogs.DialogType dtype, AccessType atype) { - String dialogTitle = ""; - if (dtype == SecurityDialogs.DialogType.CERT_WARNING) { - if (atype == AccessType.VERIFIED) - dialogTitle = "Security Approval Required"; - else - dialogTitle = "Security Warning"; - } else if (dtype == SecurityDialogs.DialogType.MORE_INFO) - dialogTitle = "More Information"; - else if (dtype == SecurityDialogs.DialogType.CERT_INFO) - dialogTitle = "Details - Certificate"; - else if (dtype == SecurityDialogs.DialogType.ACCESS_WARNING) - dialogTitle = "Security Warning"; - else if (dtype == SecurityDialogs.DialogType.APPLET_WARNING) - dialogTitle = "Applet Warning"; - else if (dtype == SecurityDialogs.DialogType.PARTIALLY_SIGNED_WARNING) - dialogTitle = "Security Warning"; - else if (dtype == SecurityDialogs.DialogType.AUTHENTICATION) - dialogTitle = "Authentication Required"; - return dialogTitle; + getViewableDialog().addWindowListener(adapter); + getViewableDialog().addWindowFocusListener(adapter); } public AccessType getAccessType() { @@ -302,111 +194,90 @@ public X509Certificate getCert() { return cert; } - /* - * find appropriate JPanel to this Dialog, based on {@link DialogType}. - */ - private SecurityDialogPanel getPanel() { - return getPanel(this); - } - - /* - * find appropriate JPanel to given Dialog, based on {@link DialogType}. - */ - static SecurityDialogPanel getPanel(SecurityDialog sd) { - return getPanel(sd.dialogType, sd); - } - - static SecurityDialogPanel getPanel(SecurityDialogs.DialogType type, SecurityDialog sd) { - SecurityDialogPanel lpanel = null; - if (type == SecurityDialogs.DialogType.CERT_WARNING) { - lpanel = new CertWarningPane(sd, sd.certVerifier, (SecurityDelegate) sd.extras[0]); - } else if (type == SecurityDialogs.DialogType.MORE_INFO) { - lpanel = new MoreInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.CERT_INFO) { - lpanel = new CertsInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.SINGLE_CERT_INFO) { - lpanel = new SingleCertInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.ACCESS_WARNING) { - lpanel = new AccessWarningPane(sd, sd.extras, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.APPLET_WARNING) { - lpanel = new AppletWarningPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.PARTIALLY_SIGNED_WARNING) { - lpanel = AppTrustWarningDialog.partiallySigned(sd, sd.file, (SecurityDelegate) sd.extras[0]); - } else if (type == SecurityDialogs.DialogType.UNSIGNED_WARNING) { - lpanel = AppTrustWarningDialog.unsigned(sd, sd.file); // Only necessary for applets on 'high security' or above - } else if (type == SecurityDialogs.DialogType.AUTHENTICATION) { - lpanel = new PasswordAuthenticationPane(sd, sd.extras); - } else if (type == SecurityDialogs.DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING) { - lpanel = new MissingPermissionsAttributePanel(sd, sd.file.getTitle(), sd.file.getNotNullProbableCodeBase().toExternalForm()); - } else if (type == SecurityDialogs.DialogType.MISSING_ALACA) { - lpanel = new MissingALACAttributePanel(sd, sd.file.getTitle(), (String) sd.extras[0], (String) sd.extras[1]); - } else if (type == SecurityDialogs.DialogType.MATCHING_ALACA) { - lpanel = AppTrustWarningDialog.matchingAlaca(sd, sd.file, (String) sd.extras[0], (String) sd.extras[1]); - } else if (type == SecurityDialogs.DialogType.SECURITY_511) { - lpanel = new InetSecurity511Panel(sd, (URL) sd.extras[0]); - } else { - throw new RuntimeException("Unknown value of " + sd.dialogType + ". Panel will be null. That's not allowed."); - } - return lpanel; - } - - /* - * Adds the appropriate JPanel to this Dialog, based on {@link DialogType}. - */ - private void installPanel() { - panel = getPanel(); - getViwableDialog().add(panel, BorderLayout.CENTER); + private static String createTitle(DialogType dtype, AccessType atype) { + String dialogTitle = ""; + if (dtype == DialogType.CERT_WARNING) { + if (atype == AccessType.VERIFIED) + dialogTitle = "Security Approval Required"; + else + dialogTitle = "Security Warning"; + } else if (dtype == DialogType.MORE_INFO) + dialogTitle = "More Information"; + else if (dtype == DialogType.CERT_INFO) + dialogTitle = "Details - Certificate"; + else if (dtype == DialogType.ACCESS_WARNING) + dialogTitle = "Security Warning"; + else if (dtype == DialogType.APPLET_WARNING) + dialogTitle = "Applet Warning"; + else if (dtype == DialogType.PARTIALLY_SIGNED_WARNING) + dialogTitle = "Security Warning"; + else if (dtype == DialogType.AUTHENTICATION) + dialogTitle = "Authentication Required"; + return dialogTitle; } - private void selectDefaultButton() { - if (panel == null) { - LOG.info("initial value panel is null"); - } else { - panel.requestFocusOnDefaultButton(); + private static SecurityDialogPanel getPanel(SecurityDialog sd) { + final DialogType type = sd.dialogType; + if (type == DialogType.CERT_WARNING) { + return new CertWarningPane(sd, sd.certVerifier, (SecurityDelegate) sd.extras[0]); + } + if (type == DialogType.MORE_INFO) { + return new MoreInfoPane(sd, sd.certVerifier); + } + if (type == DialogType.CERT_INFO) { + return new CertsInfoPane(sd, sd.certVerifier); + } + if (type == DialogType.SINGLE_CERT_INFO) { + return new SingleCertInfoPane(sd, sd.certVerifier); + } + if (type == DialogType.ACCESS_WARNING) { + return new AccessWarningPane(sd, sd.extras, sd.certVerifier); + } + if (type == DialogType.APPLET_WARNING) { + return new AppletWarningPane(sd, sd.certVerifier); + } + if (type == DialogType.PARTIALLY_SIGNED_WARNING) { + return AppTrustWarningDialog.partiallySigned(sd, sd.file, (SecurityDelegate) sd.extras[0]); + } + if (type == DialogType.UNSIGNED_WARNING) { + return AppTrustWarningDialog.unsigned(sd, sd.file); // Only necessary for applets on 'high security' or above } + if (type == DialogType.AUTHENTICATION) { + return new PasswordAuthenticationPane(sd, sd.extras); + } + if (type == DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING) { + final String codeBase = sd.file.getNotNullProbableCodeBase().toExternalForm(); + return new MissingPermissionsAttributePanel(sd, sd.file.getTitle(), codeBase); + } + if (type == DialogType.MISSING_ALACA) { + return new MissingALACAttributePanel(sd, sd.file.getTitle(), (String) sd.extras[0], (String) sd.extras[1]); + } + if (type == DialogType.MATCHING_ALACA) { + return AppTrustWarningDialog.matchingAlaca(sd, sd.file, (String) sd.extras[0], (String) sd.extras[1]); + } + if (type == DialogType.SECURITY_511) { + return new InetSecurity511Panel(sd, (URL) sd.extras[0]); + } + throw new RuntimeException("Unknown value of " + sd.dialogType + ". Panel will be null. That's not allowed."); } public void setValue(DialogResult value) { - LOG.debug("Setting value: {}", value); this.value = value; } public DialogResult getValue() { - LOG.debug("Returning value: {}", value); return value; } - - public boolean requiresSignedJNLPWarning() - { + public boolean requiresSignedJNLPWarning() { return requiresSignedJNLPWarning; } - DialogResult getDefaultNegativeAnswer() { - return panel.getDefaultNegativeAnswer(); - } - - DialogResult getDefaultPositiveAnswer() { - return panel.getDefaultPositiveAnswer(); + public ViewableDialog getViewableDialog() { + return viewableDialog; } - String getText() { - return panel.getText(); - } - - DialogResult readFromStdIn(String what){ - return panel.readFromStdIn(what); - } - - String helpToStdIn(){ - return panel.helpToStdIn(); - } - - public ViwableDialog getViwableDialog() { - return viwableDialog; - } - - public SecurityDialogPanel getSecurityDialogPanel(){ + public SecurityDialogPanel getSecurityDialogPanel() { return panel; } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java index 4b2bfa939..24f5ac6e1 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java @@ -37,6 +37,8 @@ import java.security.cert.X509Certificate; import java.util.concurrent.Semaphore; import javax.swing.JDialog; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.DialogType; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.security.AccessType; @@ -57,7 +59,7 @@ public SecurityDialogMessage(JNLPFile file) { * These fields contain information need to display the correct dialog type */ - public SecurityDialogs.DialogType dialogType; + public DialogType dialogType; public AccessType accessType; //all information dialogs needs are in file. //The only known exception is, and should remain, showAuthenticationPrompt diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java index 6be4f0c76..2bf8bd564 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java @@ -44,6 +44,7 @@ import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.sourceforge.jnlp.config.ConfigurationConstants; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.security.CertificateUtils; import net.sourceforge.jnlp.util.logging.OutputController; import sun.awt.AppContext; @@ -52,6 +53,7 @@ import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.cert.X509Certificate; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -128,7 +130,7 @@ protected void handleMessage(final SecurityDialogMessage message) { } else { if (!shouldPromptUser()) { - message.userResponse = dialog.getDefaultNegativeAnswer(); + message.userResponse = dialog.getSecurityDialogPanel().getDefaultNegativeAnswer(); unlockMessagesClient(message); } else if (isHeadless()) { processMessageInHeadless(dialog, message); @@ -141,12 +143,12 @@ protected void handleMessage(final SecurityDialogMessage message) { private boolean processAutomatedAnswers(final SecurityDialogMessage message, final SecurityDialog dialog) { if (isXtrustNone()) { - message.userResponse = dialog.getDefaultNegativeAnswer(); + message.userResponse = dialog.getSecurityDialogPanel().getDefaultNegativeAnswer(); unlockMessagesClient(message); return true; } if (isXtrustAll()) { - message.userResponse = dialog.getDefaultPositiveAnswer(); + message.userResponse = dialog.getSecurityDialogPanel().getDefaultPositiveAnswer(); unlockMessagesClient(message); return true; } @@ -154,7 +156,7 @@ private boolean processAutomatedAnswers(final SecurityDialogMessage message, fin } private void processMessageInGui(final SecurityDialog dialog, final RememberableDialog found, final SecurityDialogMessage message) { - dialog.getViwableDialog().addActionListener(new ActionListener() { + dialog.getViewableDialog().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -168,7 +170,7 @@ public void actionPerformed(ActionEvent e) { } }); - dialog.getViwableDialog().show(); + dialog.getViewableDialog().show(); } private void processMessageInHeadless(final SecurityDialog dialog, final SecurityDialogMessage message) { @@ -178,10 +180,10 @@ private void processMessageInHeadless(final SecurityDialog dialog, final Securit do { try { if (repeatAll){ - OutputController.getLogger().printOutLn(dialog.getText()); + OutputController.getLogger().printOutLn(dialog.getSecurityDialogPanel().getText()); } OutputController.getLogger().printOutLn(Translator.R("HeadlessDialogues")); - OutputController.getLogger().printOutLn(dialog.helpToStdIn()); + OutputController.getLogger().printOutLn(dialog.getSecurityDialogPanel().helpToStdIn()); String s = OutputController.getLogger().readLine(); if (s == null) { throw new IOException("Stream closed"); @@ -200,7 +202,7 @@ private void processMessageInHeadless(final SecurityDialog dialog, final Securit remember = true; s=s.substring(2); } - message.userResponse = dialog.readFromStdIn(s); + message.userResponse = dialog.getSecurityDialogPanel().readFromStdIn(s); keepGoing = false; try { String value = ""; @@ -210,7 +212,7 @@ private void processMessageInHeadless(final SecurityDialog dialog, final Securit if (dialog.getSecurityDialogPanel() instanceof CertWarningPane) { CertWarningPane cp = (CertWarningPane) (dialog.getSecurityDialogPanel()); if (remember) { - cp.saveCert(); + CertificateUtils.saveCertificate((X509Certificate) dialog.getCertVerifier().getPublisher(null)); } } RememberDialog.getInstance().setOrUpdateRememberedState(dialog, codebase, new SavedRememberAction(RememberDialog.createAction(remember, message.userResponse), value)); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogPanel.java index d802c8233..653222315 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogPanel.java @@ -75,15 +75,6 @@ public SecurityDialogPanel(SecurityDialog dialog) { this.setLayout(new BorderLayout()); } - /** - * Needed to get word wrap working in JLabels. - * @param s string to be wrapped to html tag - * @return - */ - public static String htmlWrap(String s) { - return "" + s + ""; - } - @Override public void setVisible(boolean aFlag) { super.setVisible(aFlag); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SetValueHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SetValueHandler.java index 05ff8124e..040f89fa2 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SetValueHandler.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SetValueHandler.java @@ -70,7 +70,7 @@ private SetValueHandler(SecurityDialog dialog, DialogResult returnValue) { @Override public void actionPerformed(ActionEvent e) { dialog.setValue(returnValue); - dialog.getViwableDialog().dispose(); + dialog.getViewableDialog().dispose(); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SingleCertInfoPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SingleCertInfoPane.java index 513c17ce2..ebab235e8 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SingleCertInfoPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SingleCertInfoPane.java @@ -43,6 +43,10 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; +/** + * @deprecated will be replaced by new security dialogs + */ +@Deprecated public class SingleCertInfoPane extends CertsInfoPane { public SingleCertInfoPane(SecurityDialog x, CertVerifier certVerifier) { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/TemporaryPermissionsButton.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/TemporaryPermissionsButton.java index 4e2b55bd9..517cda67c 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/TemporaryPermissionsButton.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/TemporaryPermissionsButton.java @@ -40,7 +40,7 @@ import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.config.PathsAndFiles; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import sun.security.provider.PolicyParser; import javax.swing.AbstractButton; @@ -71,7 +71,6 @@ public class TemporaryPermissionsButton extends JButton { private final JButton linkedButton; private PolicyEditor.PolicyEditorWindow policyEditorWindow = null; private final JNLPFile file; - private final SecurityDelegate securityDelegate; private final Collection temporaryPermissions = new HashSet<>(); public TemporaryPermissionsButton(final JNLPFile file, final SecurityDelegate securityDelegate, final JButton linkedButton) { @@ -84,7 +83,6 @@ public TemporaryPermissionsButton(final JNLPFile file, final SecurityDelegate se this.menu = createPolicyPermissionsMenu(); this.linkedButton = linkedButton; this.file = file; - this.securityDelegate = securityDelegate; if (file == null || securityDelegate == null || linkedButton == null) { this.setEnabled(false); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java similarity index 98% rename from core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java rename to core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java index 8ff3bf791..34fd5c67d 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java @@ -54,17 +54,17 @@ * It is accepting commons setters for jdialog, but actually applying them right before it is created. * Obviously it do not have getters, but jdialog itself should not be keeper of any information. SecurityPanel is. */ -public class ViwableDialog { +public class ViewableDialog { private JDialog jd = null; List operations = new ArrayList(); - public ViwableDialog() { + public ViewableDialog() { } private JDialog createJDialog() { jd = new JDialog(); - jd.setName("ViwableDialog"); + jd.setName("ViewableDialog"); SwingUtils.info(jd); jd.setIconImages(ImageResources.INSTANCE.getApplicationImages()); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java index 141864f72..3e918813e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java @@ -33,7 +33,7 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.AppletSecurityActions; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.ExecuteAppletAction; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.RememberableDialog; @@ -42,11 +42,10 @@ import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNo; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import net.sourceforge.jnlp.security.CertVerifier; import net.sourceforge.jnlp.util.UrlUtils; @@ -196,28 +195,6 @@ private static List getJars(JNLPFile file) { return result; } - public static void checkUnsignedWithUserIfRequired(JNLPFile file) throws LaunchException { - - if (unsignedAppletsAreForbidden()) { - LOG.debug("Not running unsigned applet at {} because unsigned applets are disallowed by security policy.", file.getCodeBase()); - throw new LaunchException(file, null, FATAL, "Application Error", "The applet was unsigned.", "The applet was unsigned.PolicyDenied"); - } - - if (!unsignedConfirmationIsRequired()) { - LOG.debug("Running unsigned applet at {} does not require confirmation according to security policy.", file.getCodeBase()); - return; - } - - YesNo warningResponse = SecurityDialogs.showUnsignedWarningDialog(file); - - LOG.debug("Decided action for unsigned applet at {} was {}", file.getCodeBase(), warningResponse); - - if (warningResponse == null || !warningResponse.compareValue(Primitive.YES)) { - throw new LaunchException(file, null, FATAL, "Application Error", "The applet was unsigned.", "The applet was unsigned.UserDenied"); - } - - } - public static void checkPartiallySignedWithUserIfRequired(SecurityDelegate securityDelegate, JNLPFile file, CertVerifier certVerifier) throws LaunchException { @@ -226,12 +203,12 @@ public static void checkPartiallySignedWithUserIfRequired(SecurityDelegate secur return; } - YesNoSandbox warningResponse = SecurityDialogs.showPartiallySignedWarningDialog(file, certVerifier, securityDelegate); + YesNoSandbox warningResponse = Dialogs.showPartiallySignedWarningDialog(file, certVerifier, securityDelegate); LOG.debug("Decided action for unsigned applet at {} was {}", file.getCodeBase(), warningResponse); if (warningResponse == null || warningResponse.compareValue(Primitive.NO)) { - throw new LaunchException(file, null, FATAL, "Application Error", "The applet was partially signed.", "The applet was partially signed.UserDenied"); + throw new LaunchException(file, null, FATAL, "Application Error", "The application was partially signed.", "The application was partially signed.UserDenied"); } //this is due to possible YesNoSandboxLimited diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningDialog.java index ac230f8ba..5f5cc4e67 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningDialog.java @@ -35,22 +35,34 @@ import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; +import net.sourceforge.jnlp.runtime.SecurityDelegate; /** * A panel that confirms that the user is OK with unsigned code running. + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class AppTrustWarningDialog { + /** + * @deprecated will be replaced by new security dialogs + * @param dialog + * @param file + * @return + */ + @Deprecated public static AppTrustWarningPanel unsigned(final SecurityDialog dialog, final JNLPFile file) { return new UnsignedAppletTrustWarningPanel(dialog, file); } + @Deprecated public static AppTrustWarningPanel partiallySigned(final SecurityDialog dialog, final JNLPFile file, final SecurityDelegate securityDelegate) { return new PartiallySignedAppTrustWarningPanel(file, dialog, securityDelegate); } - + + @Deprecated public static AppTrustWarningPanel matchingAlaca(SecurityDialog x, JNLPFile file, String codebase, String remoteUrls) { return new MatchingALACAttributePanel(x, file, codebase, remoteUrls); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanel.java index f827314b8..cf18c5658 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanel.java @@ -74,15 +74,19 @@ import java.util.List; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; -/* +/** * This class is meant to provide a common layout and functionality for warning dialogs * that appear when the user needs to confirm the running of applets/applications. * Subclasses include UnsignedAppletTrustWarningPanel, for unsigned plugin applets, and * PartiallySignedAppTrustWarningPanel, for partially signed JNLP applications as well as * plugin applets. New implementations should be added to the unit test at * unit/net/sourceforge/jnlp/security/AppTrustWarningPanelTest + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public abstract class AppTrustWarningPanel extends SecurityDialogPanel implements RememberableDialog{ private final static Logger LOG = LoggerFactory.getLogger(AppTrustWarningPanel.class); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/MatchingALACAttributePanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/MatchingALACAttributePanel.java index 3ab9d7959..17b82650f 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/MatchingALACAttributePanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/MatchingALACAttributePanel.java @@ -44,10 +44,14 @@ import java.awt.Dimension; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library + * + * @deprecated will be replaced by new security dialogs */ +@Deprecated public class MatchingALACAttributePanel extends AppTrustWarningPanel { private final String title; @@ -62,7 +66,7 @@ public MatchingALACAttributePanel(SecurityDialog securityDialog, JNLPFile file, TOP_PANEL_HEIGHT = 250; addComponents(); if (securityDialog != null) { - securityDialog.getViwableDialog().setMinimumSize(new Dimension(600, 400)); + securityDialog.getViewableDialog().setMinimumSize(new Dimension(600, 400)); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/PartiallySignedAppTrustWarningPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/PartiallySignedAppTrustWarningPanel.java index 85030b010..5ca9ecec8 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/PartiallySignedAppTrustWarningPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/PartiallySignedAppTrustWarningPanel.java @@ -34,19 +34,19 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.apptrustwarningpanel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SetValueHandler; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.TemporaryPermissionsButton; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.ExecuteAppletAction; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import net.sourceforge.jnlp.security.SecurityUtil; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletActionEntry; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.ExecuteAppletAction; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SetValueHandler; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.SecurityDelegate; +import net.sourceforge.jnlp.security.SecurityUtil; +import net.sourceforge.jnlp.signing.JarCertVerifier; import net.sourceforge.jnlp.tools.CertInformation; -import net.sourceforge.jnlp.tools.JarCertVerifier; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -55,7 +55,12 @@ import java.security.cert.X509Certificate; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; +/** + * @deprecated will be replaced by new security dialogs + */ +@Deprecated public class PartiallySignedAppTrustWarningPanel extends AppTrustWarningPanel { private final JarCertVerifier jcv; @@ -70,14 +75,14 @@ public PartiallySignedAppTrustWarningPanel(JNLPFile file, SecurityDialog securit sandboxButton = new JButton(); sandboxButton.setText(R("ButSandbox")); sandboxButton.addActionListener(SetValueHandler.createSetValueListener(parent, - YesNoSandbox.sandbox())); + YesNoSandbox.sandbox())); advancedOptionsButton = new TemporaryPermissionsButton(file, securityDelegate, sandboxButton); buttons.add(1, sandboxButton); buttons.add(2, advancedOptionsButton); addComponents(); - securityDialog.getViwableDialog().setMinimumSize(new Dimension(600, 400)); + securityDialog.getViewableDialog().setMinimumSize(new Dimension(600, 400)); } private String getAppletInfo() { @@ -98,7 +103,7 @@ private String getAppletInfo() { } catch (Exception e) { } - return "
    " + R("Publisher") + ": " + publisher + "
    " + R("From") + ": " + from + ""; + return "
    " + R("Publisher") + ": " + publisher + "
    " + R("From") + ": " + from + ""; } private String getSigningInfo() { @@ -115,8 +120,7 @@ private String getSigningInfo() { @Override protected ImageIcon getInfoImage() { - final String location = "net/sourceforge/jnlp/resources/warning.png"; - return new ImageIcon(ClassLoader.getSystemClassLoader().getResource(location)); + return ImageGallery.WARNING.asImageIcon(); } protected static String getTopPanelTextKey() { @@ -133,7 +137,7 @@ protected static String getQuestionPanelTextKey() { @Override protected String getTopPanelText() { - return SecurityDialogPanel.htmlWrap(R(getTopPanelTextKey())); + return htmlWrap(R(getTopPanelTextKey())); } @Override @@ -141,7 +145,7 @@ protected String getInfoPanelText() { String text = getAppletInfo(); text += "

    " + R(getInfoPanelTextKey(), file.getCodeBase(), file.getSourceLocation()); text += "

    " + getSigningInfo(); - UnsignedAppletActionEntry rememberedEntry = UnsignedAppletTrustConfirmation.getStoredEntry(file, this.getClass()); + UnsignedAppletActionEntry rememberedEntry = UnsignedAppletTrustConfirmation.getStoredEntry(file, this.getClass()); if (rememberedEntry != null) { ExecuteAppletAction rememberedAction = rememberedEntry.getAppletSecurityActions().getAction(this.getClass()); if (rememberedAction == ExecuteAppletAction.YES) { @@ -150,15 +154,15 @@ protected String getInfoPanelText() { text += "
    " + R("SUnsignedRejectedBefore", rememberedEntry.getLocalisedTimeStamp()); } } - return SecurityDialogPanel.htmlWrap(text); + return htmlWrap(text); } @Override protected String getQuestionPanelText() { - return SecurityDialogPanel.htmlWrap(R(getQuestionPanelTextKey())); + return htmlWrap(R(getQuestionPanelTextKey())); } - @Override + @Override public DialogResult readValue(String s) { return YesNoSandbox.readValue(s); } @@ -177,6 +181,7 @@ public DialogResult getDefaultPositiveAnswer() { public DialogResult readFromStdIn(String what) { return YesNoSandbox.readValue(what); } + @Override public String helpToStdIn() { return YesNoSandbox.sandbox().getAllowedValues().toString(); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/UnsignedAppletTrustWarningPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/UnsignedAppletTrustWarningPanel.java index 75fb5bc76..1d73a54db 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/UnsignedAppletTrustWarningPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/UnsignedAppletTrustWarningPanel.java @@ -34,7 +34,6 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.apptrustwarningpanel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialog; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletActionEntry; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.ExecuteAppletAction; @@ -44,8 +43,12 @@ import java.awt.Dimension; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; - +/** + * @deprecated will be replaced by new security dialogs + */ +@Deprecated public class UnsignedAppletTrustWarningPanel extends AppTrustWarningPanel { public UnsignedAppletTrustWarningPanel(SecurityDialog securityDialog, final JNLPFile file) { @@ -53,7 +56,7 @@ public UnsignedAppletTrustWarningPanel(SecurityDialog securityDialog, final JNLP this.INFO_PANEL_HEIGHT = 250; addComponents(); if (securityDialog != null) { - securityDialog.getViwableDialog().setMinimumSize(new Dimension(600, 400)); + securityDialog.getViewableDialog().setMinimumSize(new Dimension(600, 400)); } } @@ -77,7 +80,7 @@ protected static String getQuestionPanelTextKey() { @Override protected String getTopPanelText() { - return SecurityDialogPanel.htmlWrap(R(getTopPanelTextKey())); + return htmlWrap(R(getTopPanelTextKey())); } @Override @@ -92,11 +95,11 @@ protected String getInfoPanelText() { text += "
    " + R("SUnsignedRejectedBefore", rememberedEntry.getLocalisedTimeStamp()); } } - return SecurityDialogPanel.htmlWrap(text); + return htmlWrap(text); } @Override protected String getQuestionPanelText() { - return SecurityDialogPanel.htmlWrap(R(getQuestionPanelTextKey())); + return htmlWrap(R(getQuestionPanelTextKey())); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/Remember.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/Remember.java new file mode 100644 index 000000000..ec3d461b6 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/Remember.java @@ -0,0 +1,7 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember; + +public enum Remember { + REMEMBER_BY_APPLICATION, + REMEMBER_BY_DOMAIN, + DO_NOT_REMEMBER +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanel.java index 54678526d..d71c8b5ea 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanel.java @@ -34,7 +34,6 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember; import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; @@ -52,6 +51,7 @@ import java.net.URL; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; public class RememberPanel extends JPanel implements RememberActionProvider { @@ -83,7 +83,7 @@ public RememberPanel(String codebase) { private JPanel createCheckBoxPanel() { JPanel checkBoxPanel = new JPanel(new BorderLayout()); - permanencyCheckBox = new JCheckBox(SecurityDialogPanel.htmlWrap(R("SRememberOption"))); + permanencyCheckBox = new JCheckBox(htmlWrap(R("SRememberOption"))); permanencyCheckBox.addActionListener(permanencyListener()); checkBoxPanel.add(permanencyCheckBox, BorderLayout.SOUTH); @@ -98,7 +98,7 @@ private JPanel createMatchOptionsPanel() { applyToAppletButton.setSelected(true); applyToAppletButton.setEnabled(false); // Start disabled until 'Remember this NumberOfArguments' is selected - applyToCodeBaseButton = new JRadioButton(SecurityDialogPanel.htmlWrap(R("SRememberCodebase", codebase))); + applyToCodeBaseButton = new JRadioButton(htmlWrap(R("SRememberCodebase", codebase))); applyToCodeBaseButton.setEnabled(false); group.add(applyToAppletButton); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanelResult.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanelResult.java index 102bd65db..0d0f21835 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanelResult.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberPanelResult.java @@ -34,7 +34,11 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember; - +/** + * remembered == false => do not remember + * remembered == true && codebase == true => by domain + * remembered == true && codebase == false => by application + */ public class RememberPanelResult { //when null, then information was not available @@ -53,9 +57,4 @@ public boolean isRemember() { public boolean isCodebase() { return codebase; } - - - - - } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberableDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberableDialog.java index f07355e65..e5f9f35af 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberableDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/remember/RememberableDialog.java @@ -50,6 +50,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * @deprecated will be replaced by new security dialogs + */ +@Deprecated public interface RememberableDialog { RememberPanelResult getRememberAction(); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/ComponentRow.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/ComponentRow.java new file mode 100644 index 000000000..e97a95921 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/ComponentRow.java @@ -0,0 +1,28 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import java.awt.GridBagConstraints; + +/** + * Row which holds a single component and spans the entire width of the grid. + */ +public class ComponentRow implements GridBagRow { + + private final JComponent component; + + public ComponentRow(JComponent component) { + this.component = component; + } + + @Override + public void addTo(JPanel panel, int row) { + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = row; + constraints.ipady = 0; + constraints.gridwidth = 3; + constraints.fill = GridBagConstraints.HORIZONTAL; + panel.add(component, constraints); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagPanelBuilder.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagPanelBuilder.java new file mode 100644 index 000000000..38df044e0 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagPanelBuilder.java @@ -0,0 +1,55 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import net.adoptopenjdk.icedteaweb.Assert; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import java.awt.GridBagLayout; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Builder for creating GridBag layouted JPanels. + */ +public class GridBagPanelBuilder { + + private final List rows = new ArrayList<>(); + + public void addRow(final GridBagRow row) { + rows.add(Assert.requireNonNull(row, "row")); + } + + public void addRows(final Collection rows) { + Assert.requireNonNull(rows, "rows").forEach(this::addRow); + } + + public void addKeyValueRow(final String key, final String value) { + rows.add(new KeyValueRow(key, value)); + } + + public void addKeyComponentRow(final String key, final JComponent component) { + rows.add(new KeyComponentRow(key, component)); + } + + public void addComponentRow(final JComponent component) { + rows.add(new ComponentRow(component)); + } + + public void addHorizontalSpacer() { + rows.add(SeparatorRow.createBlankRow()); + } + + public void addHorizontalLine() { + rows.add(SeparatorRow.createHorizontalLine()); + } + + public JPanel createGrid() { + final JPanel result = new JPanel(new GridBagLayout()); + final int numRows = rows.size(); + for (int i = 0; i < numRows; i++) { + rows.get(i).addTo(result, i); + } + return result; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagRow.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagRow.java new file mode 100644 index 000000000..5180e0a55 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/GridBagRow.java @@ -0,0 +1,10 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import javax.swing.JPanel; + +/** + * API of a single row in a GridBag layouted Panel. + */ +public interface GridBagRow { + void addTo(JPanel panel, int row); +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyComponentRow.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyComponentRow.java new file mode 100644 index 000000000..5c11197e2 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyComponentRow.java @@ -0,0 +1,47 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.GridBagConstraints; + +/** + * A row with a key on the left and a component on the right. + */ +public class KeyComponentRow implements GridBagRow { + + public final String key; + public final JComponent component; + + public KeyComponentRow(final String key, final JComponent component) { + this.key = key; + this.component = component; + } + + @Override + public void addTo(JPanel panel, int row) { + final JLabel keyLabel = new JLabel(key + ":"); + final GridBagConstraints keyLabelConstraints = createConstraint(row, 0); + keyLabel.setHorizontalAlignment(SwingConstants.RIGHT); + panel.add(keyLabel, keyLabelConstraints); + + final JPanel separatorPanel = new JPanel(); + final GridBagConstraints separatorPanelConstraints = createConstraint(row, 1); + separatorPanel.setSize(5, 0); + panel.add(separatorPanel, separatorPanelConstraints); + + final GridBagConstraints valueLabelConstraints = createConstraint(row, 2); + valueLabelConstraints.weightx = 1; + panel.add(component, valueLabelConstraints); + } + + private GridBagConstraints createConstraint(int row, int column) { + final GridBagConstraints result = new GridBagConstraints(); + result.gridx = column; + result.gridy = row; + result.ipady = 5; + result.fill = GridBagConstraints.HORIZONTAL; + return result; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyValueRow.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyValueRow.java new file mode 100644 index 000000000..553d3f31b --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/KeyValueRow.java @@ -0,0 +1,13 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import javax.swing.JLabel; + +/** + * A row with a key on the left and a value on the right. + */ +public class KeyValueRow extends KeyComponentRow { + + public KeyValueRow(String key, String value) { + super(key, new JLabel(value)); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/SeparatorRow.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/SeparatorRow.java new file mode 100644 index 000000000..2cd6df32e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/gridbag/SeparatorRow.java @@ -0,0 +1,37 @@ +package net.adoptopenjdk.icedteaweb.client.util.gridbag; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import java.awt.GridBagConstraints; + +/** + * Row which acts as a spacer between other rows and spans the entire width of the grid. + */ +public class SeparatorRow implements GridBagRow { + + public static SeparatorRow createBlankRow() { + return new SeparatorRow(new JPanel()); + } + + public static SeparatorRow createHorizontalLine() { + return new SeparatorRow(new JSeparator()); + } + + private final JComponent separator; + + private SeparatorRow(JComponent separator) { + this.separator = separator; + } + + @Override + public void addTo(JPanel panel, int row) { + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = row; + constraints.ipady = 5; + constraints.gridwidth = 3; + constraints.fill = GridBagConstraints.HORIZONTAL; + panel.add(separator, constraints); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtil.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtil.java new file mode 100644 index 000000000..a7d2ad318 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtil.java @@ -0,0 +1,48 @@ +package net.adoptopenjdk.icedteaweb.client.util.html; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; + +import java.net.URL; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class HtmlUtil { + private static final Translator TRANSLATOR = Translator.getInstance(); + + public static String unorderedListOf(Set namedUrls, int maxDisplayed) { + return unorderedListOf(namedUrls.stream() + .map(url -> NamedUrl.of(url.toString(), url)) + .collect(Collectors.toList()), maxDisplayed); + } + + public static String unorderedListOf(List namedUrls, int maxDisplayed) { + if (namedUrls == null || namedUrls.isEmpty() || maxDisplayed <= 0) { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append("
      "); + + namedUrls.stream() + .limit(maxDisplayed) + .forEach(url -> { + sb.append("
    • ") + .append("") + .append(url.getName()) + .append("") + .append("
    • "); + }); + + if (namedUrls.size() > maxDisplayed) { + sb.append("
    • ") + .append(TRANSLATOR.translate("AndMore", namedUrls.size() - maxDisplayed)) + .append("
    • "); + } + + sb.append("
    "); + + return sb.toString(); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/NamedUrl.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/NamedUrl.java new file mode 100644 index 000000000..715db7e81 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/util/html/NamedUrl.java @@ -0,0 +1,25 @@ +package net.adoptopenjdk.icedteaweb.client.util.html; + +import java.net.URL; + +public class NamedUrl { + private String name; + private URL url; + + public NamedUrl(final String name, final URL url) { + this.name = name; + this.url = url; + } + + public static NamedUrl of(final String name, final URL url) { + return new NamedUrl(name, url); + } + + public String getName() { + return name; + } + + public URL getUrl() { + return url; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/ValidatorFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/ValidatorFactory.java index 555dbb00d..ae4c51652 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/ValidatorFactory.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/ValidatorFactory.java @@ -14,6 +14,9 @@ import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; import net.sourceforge.jnlp.config.DeploymentConfiguration; +import java.util.Arrays; +import java.util.stream.Collectors; + /** * Provides {@link net.adoptopenjdk.icedteaweb.config.validators.ValueValidator} implementations for some common value types * @@ -44,14 +47,11 @@ public static ValueValidator createFilePathValidator() { public static ValueValidator createBrowserPathValidator() { return new ValueValidator() { @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { if (value == null) { return; } - if (!(value instanceof String)) { - throw new IllegalArgumentException("Value should be string!"); - } - if (ValidatorUtils.verifyFileOrCommand((String)value) == null){ + if (ValidatorUtils.verifyFileOrCommand(value) == null){ //just warn? throw new IllegalArgumentException("Value should be file, or on PATH, or known keyword. See possible values."); } @@ -86,6 +86,20 @@ public static ValueValidator createStringValidator(final String[] validValues) { return new StringValueValidator(validValues); } + /** + * Returns a {@link net.adoptopenjdk.icedteaweb.config.validators.ValueValidator} that checks if an object is a string from + * one of the provided Strings. + * @param validValues an array of enums which are considered valid + * @return validator for given strings + */ + public static ValueValidator createStringValidator(final Enum[] validValues) { + final String[] validStrings = Arrays.stream(validValues) + .map(Enum::name) + .collect(Collectors.toList()) + .toArray(new String[0]); + return new StringValueValidator(validStrings); + } + /** * Returns a {@link net.adoptopenjdk.icedteaweb.config.validators.ValueValidator} that checks if an object is a string from * one of the provided single NumberOfArguments Strings or a combination from diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/BooleanValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/BooleanValidator.java index 3104a3d67..aace97d1c 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/BooleanValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/BooleanValidator.java @@ -10,12 +10,9 @@ public class BooleanValidator implements ValueValidator { @Override - public void validate(final Object value) throws IllegalArgumentException { - if(value instanceof Boolean) { - return; - } - if (value instanceof String) { - final String lower = ((String) value).toLowerCase(Locale.ENGLISH); + public void validate(final String value) throws IllegalArgumentException { + if (value != null) { + final String lower = value.toLowerCase(Locale.ENGLISH); if (lower.equals(Boolean.TRUE.toString()) || (lower.equals(Boolean.FALSE.toString()))) { return; diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/FilePathValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/FilePathValidator.java index c1060d62f..145f26a89 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/FilePathValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/FilePathValidator.java @@ -12,18 +12,12 @@ public class FilePathValidator implements ValueValidator { @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { if (value == null) { return; } - if (!(value instanceof String)) { - throw new IllegalArgumentException("Value should be string!"); - } - - final String possibleFile = (String) value; - - if (!new File(possibleFile).isAbsolute()) { + if (!new File(value).isAbsolute()) { throw new IllegalArgumentException("File must be absolute"); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/MultipleStringValueValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/MultipleStringValueValidator.java index da1b3b35b..096be4729 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/MultipleStringValueValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/MultipleStringValueValidator.java @@ -1,7 +1,5 @@ package net.adoptopenjdk.icedteaweb.config.validators; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; - import java.util.Arrays; import java.util.List; @@ -21,22 +19,21 @@ public MultipleStringValueValidator(final String[] singleOptions, final String[] } @Override - public void validate(final Object value) throws IllegalArgumentException { - if (!(value instanceof String)) { - throw new IllegalArgumentException("Must be a string"); + public void validate(final String value) throws IllegalArgumentException { + if (value == null) { + throw new IllegalArgumentException("Must not be null"); } - final String stringVal = (String) value; boolean found = false; for (final String knownVal : singleOptions) { - if (knownVal.equals(stringVal)) { + if (knownVal.equals(value)) { found = true; break; } } if (!found) { - final List possibleCombo = ValidatorUtils.splitCombination(stringVal); + final List possibleCombo = ValidatorUtils.splitCombination(value); for (final String val : possibleCombo) { if (comboOptionsContains(val)) { found = true; @@ -47,7 +44,7 @@ public void validate(final Object value) throws IllegalArgumentException { } if (!found) { - throw new IllegalArgumentException("Invalid value found: '" + stringVal + "'"); + throw new IllegalArgumentException("Invalid value found: '" + value + "'"); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/NotBlankValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/NotBlankValidator.java index 69779b600..0561100c2 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/NotBlankValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/NotBlankValidator.java @@ -10,12 +10,8 @@ public class NotBlankValidator implements ValueValidator { @Override - public void validate(final Object value) throws IllegalArgumentException { - if (!(value instanceof String)) { - throw new IllegalArgumentException("Must be a string"); - } - - if (StringUtils.isBlank((String) value)) { + public void validate(final String value) throws IllegalArgumentException { + if (StringUtils.isBlank(value)) { throw new IllegalArgumentException("Must not be blank"); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/PortValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/PortValidator.java index cd72efd1f..be2899a70 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/PortValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/PortValidator.java @@ -13,7 +13,7 @@ public PortValidator() { } @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { if (value == null) { return; // null for a port tells ITW to use the default port } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RangedIntegerValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RangedIntegerValidator.java index 2e43f604d..2f84c7158 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RangedIntegerValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RangedIntegerValidator.java @@ -18,26 +18,18 @@ public RangedIntegerValidator(final int low, final int high) { } @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { + if (value == null) { + throw new IllegalArgumentException("Must not be null"); + } - long actualValue = 0; try { - if (value instanceof String) { - actualValue = Long.valueOf((String) value); - } else if (value instanceof Integer) { - actualValue = (Integer) value; - } else if (value instanceof Long) { - actualValue = (Long) value; - } else { - throw new IllegalArgumentException("Must be an integer"); + final long actualValue = Long.parseLong(value); + if (actualValue < low || actualValue > high) { + throw new IllegalArgumentException("Not in range from " + low + " to " + high); } } catch (final NumberFormatException e) { throw new IllegalArgumentException("Must be an integer", e); - - } - - if (actualValue < low || actualValue > high) { - throw new IllegalArgumentException("Not in range from " + low + " to " + high); } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RustCpValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RustCpValidator.java index 0f182fc37..f6a3da1d9 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RustCpValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/RustCpValidator.java @@ -6,7 +6,7 @@ public class RustCpValidator implements ValueValidator { @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { //can't be wrong... //but we need that getPossibleValues description } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/SecurityValueValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/SecurityValueValidator.java deleted file mode 100644 index ad2e4bb5e..000000000 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/SecurityValueValidator.java +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright (C) 2013 Red Hat, Inc. - - This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ -package net.adoptopenjdk.icedteaweb.config.validators; - -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; - -public class SecurityValueValidator implements ValueValidator { - - public SecurityValueValidator() { - } - - @Override - public void validate(final Object value) throws IllegalArgumentException { - if (value == null) { - // null is correct, it means it is not user set - // and so default should be used whatever it is - // returning to prevent NPE in fromString - return; - } - if (value instanceof AppletSecurityLevel) { - //?? - return; - } - if (!(value instanceof String)) { - throw new IllegalArgumentException("Expected was String, was " + value.getClass()); - } - try { - final AppletSecurityLevel validated = AppletSecurityLevel.fromString((String) value); - if (validated == null) { - throw new IllegalArgumentException("Result cannot be null, was"); - } - //thrown by fromString - } catch (final RuntimeException ex) { - throw new IllegalArgumentException("Error in validation", ex); - } - } - - @Override - public String getPossibleValues() { - return AppletSecurityLevel.allToString(); - } - -} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/StringValueValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/StringValueValidator.java index b512d8e1e..2ebb5025b 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/StringValueValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/StringValueValidator.java @@ -14,15 +14,14 @@ public StringValueValidator(final String[] acceptableOptions) { } @Override - public void validate(final Object value) throws IllegalArgumentException { - if (!(value instanceof String)) { - throw new IllegalArgumentException("Must be a string"); + public void validate(final String value) throws IllegalArgumentException { + if (value == null) { + throw new IllegalArgumentException("Must not be null"); } - String stringVal = (String) value; boolean found = false; for (String knownVal : options) { - if (knownVal.equals(stringVal)) { + if (knownVal.equals(value)) { found = true; break; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/UrlValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/UrlValidator.java index f2f6df6fc..90c0f341d 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/UrlValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/UrlValidator.java @@ -10,12 +10,12 @@ public class UrlValidator implements ValueValidator { @Override - public void validate(final Object value) throws IllegalArgumentException { + public void validate(final String value) throws IllegalArgumentException { if (value == null) { return; } try { - new URL((String) value); + new URL(value); } catch (final Exception e) { throw new IllegalArgumentException("Not a valid URL", e); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/ValueValidator.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/ValueValidator.java index de4c110ae..6fd07b066 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/ValueValidator.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/config/validators/ValueValidator.java @@ -52,7 +52,7 @@ public interface ValueValidator { * @param value The object to validate * @throws IllegalArgumentException if the value is invalid */ - void validate(Object value) throws IllegalArgumentException; + void validate(String value) throws IllegalArgumentException; /** * Returns a string describing possible values in human-readable form that diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/extension/InstallerDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/extension/InstallerDesc.java index 40a9e10ff..af0b7b7ad 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/extension/InstallerDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/extension/InstallerDesc.java @@ -53,15 +53,6 @@ public class InstallerDesc implements EntryPoint { */ private final String progressClass; - /** - * Creates an installer descriptor element. - * - * @param mainClass the fully qualified name of the class containing the main method of the application - */ - public InstallerDesc(final String mainClass) { - this(mainClass, null); - } - /** * Creates an installer descriptor element. * diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDesc.java index d9e6c9cbf..8792af151 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDesc.java @@ -21,9 +21,7 @@ import java.net.URL; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * The extension element. @@ -32,14 +30,12 @@ * @version $Revision: 1.8 $ */ public class ExtensionDesc { + public static final String EXT_DOWNLOAD_ELEMENT = "ext-download"; - public static final String HREF_ATTRIBUTE = "href"; - public static final String DOWNLOAD_ATTRIBUTE = "download"; - public static final String EXT_PART_ATTRIBUTE = "ext-part"; + public static final String HREF_ATTRIBUTE = "href"; public static final String NAME_ATTRIBUTE = "name"; public static final String VERSION_ATTRIBUTE = "version"; - public static final String PART_ATTRIBUTE = "part"; /** the extension name */ private final String name; @@ -52,11 +48,8 @@ public class ExtensionDesc { /** the location of the extension JNLP file */ private final URL location; - /** map from ext-part to local part */ - private final Map extToPart = new HashMap<>(); - /** eager ext parts */ - private final List eagerExtParts = new ArrayList<>(); + private final List downloads = new ArrayList<>(); /** * Create an extension descriptor. @@ -77,15 +70,14 @@ public ExtensionDesc(String name, VersionString version, URL location) { * will be downloaded before the application is launched if the * lazy value is false or the part is empty or null. * - * @param extPart the part name in the extension file - * @param part the part name in the main file - * @param lazy whether to load the part before launching + * @param download the extension download description */ - public void addPart(String extPart, String part, boolean lazy) { - extToPart.put(extPart, part); + public void addDownload(ExtensionDownloadDesc download) { + downloads.add(download); + } - if (!lazy || part == null || part.length() == 0) - eagerExtParts.add(extPart); + public List getDownloads() { + return downloads; } /** diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDownloadDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDownloadDesc.java new file mode 100644 index 000000000..a37584c52 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ExtensionDownloadDesc.java @@ -0,0 +1,59 @@ +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.jnlp.element.resource; + +import net.adoptopenjdk.icedteaweb.StringUtils; + +/** + * The extension download element. + */ +public class ExtensionDownloadDesc { + public static final String PART_ATTRIBUTE = "part"; + public static final String EXT_PART_ATTRIBUTE = "ext-part"; + public static final String DOWNLOAD_ATTRIBUTE = "download"; + + private final String extPart; + private final String part; + private final boolean lazy; + + + /** + * Create an extension download descriptor. + * + * @param extPart the name of the part in the extension JNLP + * @param part the name of the part in the current JNLP + * @param lazy download the extension part lazy + */ + public ExtensionDownloadDesc(String extPart, String part, boolean lazy) { + this.extPart = extPart; + this.part = part; + this.lazy = lazy && !StringUtils.isBlank(part); + } + + public String getExtPart() { + return extPart; + } + + public String getPart() { + return part; + } + + public boolean isLazy() { + return lazy; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JARDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JARDesc.java index 5b2ff42d9..7ea322292 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JARDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JARDesc.java @@ -19,6 +19,7 @@ import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; import java.net.URL; +import java.util.StringJoiner; /** * The JAR element. @@ -42,7 +43,7 @@ public class JARDesc { /** * The required JAR version. The version attribute can specify an exact version or * a list of versions (version string). See JSR-56, section 3.4 for details. */ - private VersionString version; + private final VersionString version; /** the part name */ private final String part; @@ -56,9 +57,6 @@ public class JARDesc { /** whether the JAR contains native libraries */ private final boolean nativeJar; - /** whether the JAR can be cached */ - private final boolean cacheable; - /** * Create a JAR descriptor. * @@ -68,16 +66,14 @@ public class JARDesc { * @param lazy whether to load the JAR on demand * @param main whether the JAR contains the main class * @param nativeJar whether the JAR contains native libraries - * @param cacheable whether the JAR can be cached or not */ - public JARDesc(final URL location, final VersionString version, final String part, final boolean lazy, final boolean main, final boolean nativeJar, final boolean cacheable) { + public JARDesc(final URL location, final VersionString version, final String part, final boolean lazy, final boolean main, final boolean nativeJar) { this.location = location; this.version = version; this.part = part; this.lazy = lazy; this.main = main; this.nativeJar = nativeJar; - this.cacheable = cacheable; } /** @@ -94,10 +90,6 @@ public VersionString getVersion() { return version; } - public void setVersion(final VersionString version) { - this.version = version; - } - /** * @return the part name, or null if not specified in the JNLP * file. @@ -143,13 +135,15 @@ public boolean isMain() { return main; } - /** - * Returns if this jar is cacheable - * - * @return Whether or not this jar is cacheable - */ - public boolean isCacheable() { - return cacheable; + @Override + public String toString() { + return new StringJoiner(", ", JARDesc.class.getSimpleName() + "[", "]") + .add("location='" + location + "'") + .add("version=" + version) + .add("part='" + part + "'") + .add("lazy=" + lazy) + .add("main=" + main) + .add("nativeJar=" + nativeJar) + .toString(); } - } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResources.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResources.java new file mode 100644 index 000000000..3a11da9fe --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResources.java @@ -0,0 +1,128 @@ +package net.adoptopenjdk.icedteaweb.jnlp.element.resource; + +import net.adoptopenjdk.icedteaweb.Assert; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static net.adoptopenjdk.icedteaweb.StringUtils.hasPrefixMatch; +import static net.sourceforge.jnlp.util.LocaleUtils.localeMatches; + +/** + * ... + */ +public class JNLPResources { + private final List resources; + + public JNLPResources(List resources) { + final ArrayList copy = new ArrayList<>(); + if (resources != null) { + copy.addAll(resources); + } + this.resources = Collections.unmodifiableList(copy); + } + + public JNLPResources filterResources(Locale locale, String os, String arch) { + final List list = resources.stream() + .filter(rescDesc -> hasPrefixMatch(os, rescDesc.getOS())) + .filter(rescDesc -> hasPrefixMatch(arch, rescDesc.getArch())) + .filter(rescDesc -> localeMatches(locale, rescDesc.getLocales())) + .collect(toList()); + return new JNLPResources(list); + } + + public List all() { + return resources; + } + + public Stream stream() { + return resources.stream(); + } + + /** + * @return the JVMs. + */ + public List getJREs() { + return getResources(JREDesc.class); + } + + + /** + * @return all of the JARs. + */ + public List getJARs() { + return getResources(JARDesc.class); + } + + /** + * @param partName the part name, null and "" equivalent + * @return the JARs with the specified part name. + */ + public List getJARs(final String partName) { + Assert.requireNonBlank(partName, "partName"); + return getJARs().stream() + .filter(jarDesc -> partName.equals(jarDesc.getPart())) + .collect(toList()); + } + + /** + * @return the Extensions. + */ + public List getExtensions() { + return getResources(ExtensionDesc.class); + } + + /** + * @return the Packages. + */ + public List getPackages() { + return getResources(PackageDesc.class); + } + + /** + * Returns the Packages that match the specified class name. + * + * @param className the fully qualified class name + * @return the PackageDesc objects matching the class name + */ + public List getPackages(final String className) { + return getPackages().stream() + .filter(pk -> pk.matches(className)) + .collect(toList()); + } + + /** + * @return the Properties as a list. + */ + public List getProperties() { + return getResources(PropertyDesc.class); + } + + /** + * @return the properties as a map. + */ + public Map getPropertiesMap() { + final Map result = new HashMap<>(); + for (PropertyDesc property : getProperties()) { + result.put(property.getKey(), property.getValue()); + } + return result; + } + + /** + * @param type of resource to be found + * @param type resource to be found + * @return all resources of the specified type. + */ + private List getResources(final Class type) { + return resources.stream() + .flatMap(resourcesDesc -> resourcesDesc.getResources(type).stream()) + .collect(toList()); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JREDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JREDesc.java index b108983e2..aa96454ec 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JREDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JREDesc.java @@ -73,7 +73,7 @@ public class JREDesc { private final List parsedArguments; /** list of ResourceDesc objects */ - private final List resources; + private final JNLPResources resources; /** * Create a JRE descriptor. @@ -96,7 +96,7 @@ public JREDesc(final VersionString version, final String vendor, final URL locat this.parsedArguments = parseArguments(vmArgs); this.initialHeapSize = checkHeapSize(initialHeapSize); this.maximumHeapSize = checkHeapSize(maximumHeapSize); - this.resources = resources; + this.resources = new JNLPResources(resources); } /** @@ -146,6 +146,10 @@ public String getInitialHeapSize() { * @return the resources defined for this JRE. */ public List getResourcesDesc() { + return resources.all(); + } + + public JNLPResources getJnlpResources() { return resources; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDesc.java index eb58e780c..65f22fac8 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDesc.java @@ -16,6 +16,8 @@ package net.adoptopenjdk.icedteaweb.jnlp.element.resource; +import net.adoptopenjdk.icedteaweb.Assert; + import java.util.Objects; /** @@ -59,22 +61,21 @@ public PackageDesc(final String name, final String part, final boolean recursive * @return whether the specified class is part of this package. * * @param className the fully qualified class name - */ public boolean matches(final String className) { - // form 1: exact class - if (Objects.equals(name, className)) { - return true; - } - // form 2: package.* - Objects.requireNonNull(className); + Assert.requireNonNull(className, "className"); if (name.endsWith(ASTERIX_SUFFIX)) { + // Form 2: name is a package name final String pkName = name.substring(0, name.length() - 1); if (className.startsWith(pkName)) { - String postfix = className.substring(pkName.length() + 1); + final String postfix = className.substring(pkName.length()); return recursive || !postfix.contains("."); } } + else { + // Form 1: name is a class name + return Objects.equals(name, className); + } return false; } @@ -100,4 +101,9 @@ public boolean isRecursive() { return recursive; } + + @Override + public String toString() { + return String.valueOf(name); + } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ResourcesDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ResourcesDesc.java index 5e82879c6..2dc12f22b 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ResourcesDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/ResourcesDesc.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; import static java.util.Arrays.asList; @@ -242,33 +243,10 @@ public JNLPFile getJNLPFile() { * @return all resources of the specified type. */ public List getResources(final Class type) { - final List result = new ArrayList<>(); - for (final Object resource : resources) { - if (resource instanceof JREDesc) { - final JREDesc jre = (JREDesc) resource; - final List descs = jre.getResourcesDesc(); - for (final ResourcesDesc desc : descs) { - result.addAll(desc.getResources(type)); - } - } - if (isWontedResource(resource, type)) { - result.add(getWontedResource(resource, type)); - } - } - - return result; - } - - private static boolean isWontedResource(final Object resource, final Class type) { - final T l = getWontedResource(resource, type); - return l != null; - } - - private static T getWontedResource(final Object resource, final Class type) { - if (type.isAssignableFrom(resource.getClass())) { - return type.cast(resource); - } - return null; + return resources.stream() + .filter(resource -> type.isAssignableFrom(resource.getClass())) + .map(type::cast) + .collect(Collectors.toList()); } /** diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/AppletPermissionLevel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/AppletPermissionLevel.java deleted file mode 100644 index 5c7c8ec03..000000000 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/AppletPermissionLevel.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2019 Karakun AG -// -// 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.adoptopenjdk.icedteaweb.jnlp.element.security; - -/** - * Specifies the level of permissions that the applet needs to run. Specify "sandbox" for the - * value to run in the sandbox. Specify "all-permissions" to run outside the sandbox. - *

    - * Applet permissions are specified in the applet html page file as sub element - * {@code } of the applet tag. - * - * If this parameter is omitted, {@code default} is assumed which is determined by - * the {@code Permissions} attribute in the manifest for the main JAR file. - * - * (see https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/applet_dev_guide.html#JSDPG709). - *

    - * or the - * {@code Permissions: sandbox} attribute in the manifest for the main JAR file. - * - * @see SecurityDesc - */ -public enum AppletPermissionLevel { - /** - * The all-permissions indicates that the RIA needs full access the the local - * system and network. - */ - ALL("all-permissions"), - - /** - * Indicates that the RIA runs in the security sandbox and does not require additional permissions. - */ - SANDBOX("sandbox"), - - /** - * Indicates that the level of permissions is determined by the {@code Permissions} attribute in the manifest - * for the main JAR file. - */ - DEFAULT("default"), - - /** - * Indicates that no applet permissions are specified. - * @deprecated - */ - @Deprecated - // consider to handle the absence of an permissions param different - // as there is no such thing as NONE in the specs - NONE(null); - - private final String value; - - AppletPermissionLevel(final String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationPermissionLevel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationEnvironment.java similarity index 75% rename from core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationPermissionLevel.java rename to core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationEnvironment.java index 2708e984f..a87bf1a0e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationPermissionLevel.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/ApplicationEnvironment.java @@ -30,31 +30,29 @@ * for a detailed specification of this class. * @see SecurityDesc */ -public enum ApplicationPermissionLevel { +public enum ApplicationEnvironment { /** - * The all-permissions indicates that the application needs full access the the local + * The all-permissions trusted environment indicates that the application needs full access the the local * system and network. */ ALL("all-permissions"), /** - * The j2ee-application-client-permissions element indicates that the application needs the set of + * The j2ee-application-client-permissions trusted environment indicates that the application needs the set of * permissions defined for a J2EE application client. */ J2EE("j2ee-application-client-permissions"), /** - * Indicates that no application permissions are specified. - * @deprecated + * Indicates that no application permissions are specified in the JNLP file. + * This indicates that the application is able to run in an untrusted (sandboxed) environment. + * As a consequence only the minimal permissions are granted. */ - @Deprecated - // consider to handle the absence of a security element in the jnlp file different - // as there is no such thing as NONE in the specs - NONE(null); + SANDBOX(null); private final String value; - ApplicationPermissionLevel(final String value) { + ApplicationEnvironment(final String value) { this.value = value; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDesc.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDesc.java index 3cf7bef31..f00291455 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDesc.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDesc.java @@ -16,84 +16,6 @@ package net.adoptopenjdk.icedteaweb.jnlp.element.security; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.NullJnlpFileException; -import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.util.UrlUtils; - -import java.awt.AWTPermission; -import java.io.FilePermission; -import java.lang.reflect.Constructor; -import java.net.SocketPermission; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.security.AllPermission; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.Policy; -import java.security.URIParameter; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.PropertyPermission; -import java.util.Set; - -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.ARRAY_LEGACY_MERGE_SORT; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_AA_FONT_SETTINGS; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_ERASE_BACKGROUND_ON_RESIZE; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_NO_ERASE_BACKGROUND; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_SYNC_LWREQUESTS; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_WINDOW_LOCATION_BY_PLATFORM; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.BROWSER; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.BROWSER_STAR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.EXIT_VM; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.FILE_SEPARATOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.HTTP_AGENT; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.HTTP_KEEP_ALIVE; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_D3D; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_DPI_AWARE; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_NO_DDRAW; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_OPENGL; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVAWS; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_CLASS_VERSION; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_PLUGIN; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_NAME; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_VENDOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_VERSION; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VENDOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VENDOR_URL; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VERSION; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JNLP; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.LINE_SEPARATOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_ARCH; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_NAME; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_VERSION; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.PATH_SEPARATOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.STOP_THREAD; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_BOLD_METAL; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_DEFAULT_LF; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_METAL_THEME; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_NO_XP; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_USE_SYSTEM_FONT; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_NAME; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_SPEC_NAME; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_SPEC_VENDOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_VENDOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_VERSION; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.WEBSTART_JAUTHENTICATOR; -import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.WEBSTART_VERSION; -import static sun.security.util.SecurityConstants.PROPERTY_READ_ACTION; -import static sun.security.util.SecurityConstants.PROPERTY_RW_ACTION; - /** * The security element. * @@ -101,394 +23,20 @@ * @version $Revision: 1.7 $ */ public class SecurityDesc { - private final static Logger LOG = LoggerFactory.getLogger(SecurityDesc.class); - public static final String SECURITY_ELEMENT = "security"; - private ApplicationPermissionLevel applicationPermissionLevel; - private AppletPermissionLevel appletPermissionLevel; - - /* - * We do not verify security here, the classloader deals with security - */ - - /** All permissions. */ - public static final Object ALL_PERMISSIONS = "All"; - - /** Applet permissions. */ - public static final Object SANDBOX_PERMISSIONS = "Sandbox"; - - /** J2EE permissions. */ - public static final Object J2EE_PERMISSIONS = "J2SE"; - - /** permissions type */ - private Object type; - - /** the download host */ - final private URL downloadHost; - - /** whether sandbox applications should get the show window without banner permission */ - private final boolean grantAwtPermissions; - - /** the JNLP file */ - private final JNLPFile file; - - private final Policy customTrustedPolicy; - - /** - * URLPermission is new in Java 8, so we use reflection to check for it to keep compatibility - * with Java 6/7. If we can't find the class or fail to construct it then we continue as usual - * without. - * - * These are saved as fields so that the reflective lookup only needs to be performed once - * when the SecurityDesc is constructed, rather than every time a call is made to - * {@link SecurityDesc#getSandBoxPermissions()}, which is called frequently. - */ - private static Class urlPermissionClass; - private static Constructor urlPermissionConstructor; - - static { - try { - urlPermissionClass = (Class) Class.forName("java.net.URLPermission"); - urlPermissionConstructor = urlPermissionClass.getDeclaredConstructor(String.class); - } catch (final ReflectiveOperationException | SecurityException e) { - LOG.error("Exception while reflectively finding URLPermission - host is probably not running Java 8+", e); - urlPermissionClass = null; - urlPermissionConstructor = null; - } - } - - // We go by the rules here: - // http://java.sun.com/docs/books/tutorial/deployment/doingMoreWithRIA/properties.html - - // Since this is security sensitive, take a conservative approach: - // Allow only what is specifically allowed, and deny everything else - - /** - * basic permissions for restricted mode - */ - private static final Permission[] j2eePermissions = { - new AWTPermission("accessClipboard"), - // disabled because we can't at this time prevent an - // application from accessing other applications' event - // queues, or even prevent access to security dialog queues. - // - // new AWTPermission("accessEventQueue"), - new RuntimePermission("exitVM"), - new RuntimePermission("loadLibrary"), - new RuntimePermission("queuePrintJob"), - new SocketPermission("*", "connect"), - new SocketPermission("localhost:1024-", "accept, listen"), - new FilePermission("*", "read, write"), - new PropertyPermission("*", PROPERTY_READ_ACTION), - }; - - /** - * basic permissions for restricted mode - */ - private static final Permission[] sandboxPermissions = { - new SocketPermission("localhost:1024-", "listen"), - // new SocketPermission("", "connect, accept"), // added by code - new PropertyPermission(ARRAY_LEGACY_MERGE_SORT, PROPERTY_RW_ACTION), - new PropertyPermission(JAVA_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_VENDOR, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_VENDOR_URL, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_CLASS_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(OS_NAME, PROPERTY_READ_ACTION), - new PropertyPermission(OS_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(OS_ARCH, PROPERTY_READ_ACTION), - new PropertyPermission(FILE_SEPARATOR, PROPERTY_READ_ACTION), - new PropertyPermission(PATH_SEPARATOR, PROPERTY_READ_ACTION), - new PropertyPermission(LINE_SEPARATOR, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_SPEC_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_SPEC_VENDOR, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_SPEC_NAME, PROPERTY_READ_ACTION), - new PropertyPermission(VM_SPEC_VENDOR, PROPERTY_READ_ACTION), - new PropertyPermission(VM_SPEC_NAME, PROPERTY_READ_ACTION), - new PropertyPermission(VM_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(VM_VENDOR, PROPERTY_READ_ACTION), - new PropertyPermission(VM_NAME, PROPERTY_READ_ACTION), - new PropertyPermission(WEBSTART_VERSION, PROPERTY_READ_ACTION), - new PropertyPermission(JAVA_PLUGIN, PROPERTY_READ_ACTION), - new PropertyPermission(JNLP, PROPERTY_RW_ACTION), - new PropertyPermission(JAVAWS, PROPERTY_RW_ACTION), - new PropertyPermission(BROWSER, PROPERTY_READ_ACTION), - new PropertyPermission(BROWSER_STAR, PROPERTY_READ_ACTION), - new RuntimePermission(EXIT_VM), - new RuntimePermission(STOP_THREAD), - // disabled because we can't at this time prevent an - // application from accessing other applications' event - // queues, or even prevent access to security dialog queues. - // - // new AWTPermission("accessEventQueue"), - }; - - /** - * basic permissions for restricted mode - * @see Secure System Properties - */ - private static final Permission[] jnlpRIAPermissions = { - new PropertyPermission(AWT_AA_FONT_SETTINGS, PROPERTY_RW_ACTION), - new PropertyPermission(HTTP_AGENT, PROPERTY_RW_ACTION), - new PropertyPermission(HTTP_KEEP_ALIVE, PROPERTY_RW_ACTION), - new PropertyPermission(AWT_SYNC_LWREQUESTS, PROPERTY_RW_ACTION), - new PropertyPermission(AWT_WINDOW_LOCATION_BY_PLATFORM, PROPERTY_RW_ACTION), - new PropertyPermission(WEBSTART_JAUTHENTICATOR, PROPERTY_RW_ACTION), - new PropertyPermission(SWING_DEFAULT_LF, PROPERTY_RW_ACTION), - new PropertyPermission(AWT_NO_ERASE_BACKGROUND, PROPERTY_RW_ACTION), - new PropertyPermission(AWT_ERASE_BACKGROUND_ON_RESIZE, PROPERTY_RW_ACTION), - new PropertyPermission(JAVA2D_D3D, PROPERTY_RW_ACTION), - new PropertyPermission(JAVA2D_DPI_AWARE, PROPERTY_RW_ACTION), - new PropertyPermission(JAVA2D_NO_DDRAW, PROPERTY_RW_ACTION), - new PropertyPermission(JAVA2D_OPENGL, PROPERTY_RW_ACTION), - new PropertyPermission(SWING_BOLD_METAL, PROPERTY_RW_ACTION), - new PropertyPermission(SWING_METAL_THEME, PROPERTY_RW_ACTION), - new PropertyPermission(SWING_NO_XP, PROPERTY_RW_ACTION), - new PropertyPermission(SWING_USE_SYSTEM_FONT, PROPERTY_RW_ACTION), - }; - - /** - * Create a security descriptor. - * - * @param file the JNLP file - * @param applicationPermissionLevel the permissions specified in the JNLP - * @param type the type of security - * @param downloadHost the download host (can always connect to) - */ - public SecurityDesc(final JNLPFile file, final ApplicationPermissionLevel applicationPermissionLevel, final Object type, final URL downloadHost) { - if (file == null) { - throw new NullJnlpFileException(); - } - this.file = file; - this.applicationPermissionLevel = applicationPermissionLevel; - this.type = type; - this.downloadHost = downloadHost; - - String key = ConfigurationConstants.KEY_SECURITY_ALLOW_HIDE_WINDOW_WARNING; - grantAwtPermissions = Boolean.valueOf(JNLPRuntime.getConfiguration().getProperty(key)); - - customTrustedPolicy = getCustomTrustedPolicy(); - } + private final ApplicationEnvironment applicationEnvironment; /** * Create a security descriptor. * - * @param file the JNLP file - * @param appletPermissionLevel the permissions specified in the JNLP - * @param type the type of security - * @param downloadHost the download host (can always connect to) - */ - public SecurityDesc(final JNLPFile file, final AppletPermissionLevel appletPermissionLevel, final Object type, final URL downloadHost) { - if (file == null) { - throw new NullJnlpFileException(); - } - this.file = file; - this.appletPermissionLevel = appletPermissionLevel; - this.type = type; - this.downloadHost = downloadHost; - - String key = ConfigurationConstants.KEY_SECURITY_ALLOW_HIDE_WINDOW_WARNING; - grantAwtPermissions = Boolean.valueOf(JNLPRuntime.getConfiguration().getProperty(key)); - - customTrustedPolicy = getCustomTrustedPolicy(); - } - - /** - * Returns a Policy object that represents a custom policy to use instead - * of granting {@link AllPermission} to a {@link CodeSource} - * - * @return a {@link Policy} object to delegate to. May be null, which - * indicates that no policy exists and AllPermissions should be granted - * instead. + * @param applicationEnvironment the permissions environment as specified in the JNLP */ - private Policy getCustomTrustedPolicy() { - String key = ConfigurationConstants.KEY_SECURITY_TRUSTED_POLICY; - String policyLocation = JNLPRuntime.getConfiguration().getProperty(key); - - Policy policy = null; - if (policyLocation != null) { - try { - URI policyUri = new URI("file://" + policyLocation); - policy = Policy.getInstance("JavaPolicy", new URIParameter(policyUri)); - } catch (Exception e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); - } - } - // return the appropriate policy, or null - return policy; - } - - /** - * @return the permissions type, one of: ALL, - * SANDBOX_PERMISSIONS, J2EE_PERMISSIONS. - */ - public Object getSecurityType() { - return type; - } - - /** - * @return a PermissionCollection containing the basic - * permissions granted depending on the security type. - * - * @param cs the CodeSource to get permissions for - */ - public PermissionCollection getPermissions(CodeSource cs) { - PermissionCollection permissions = getSandBoxPermissions(); - - // discard sandbox, give all - if (ALL_PERMISSIONS.equals(type)) { - permissions = new Permissions(); - if (customTrustedPolicy == null) { - permissions.add(new AllPermission()); - return permissions; - } else { - return customTrustedPolicy.getPermissions(cs); - } - } - - // add j2ee to sandbox if needed - if (J2EE_PERMISSIONS.equals(type)) - for (Permission j2eePermission : j2eePermissions) { - permissions.add(j2eePermission); - } - - return permissions; - } - - public ApplicationPermissionLevel getApplicationPermissionLevel() { - return applicationPermissionLevel; - } - - public AppletPermissionLevel getAppletPermissionLevel() { - return appletPermissionLevel; - } - - /** - * @return a PermissionCollection containing the sandbox permissions - */ - public PermissionCollection getSandBoxPermissions() { - final Permissions permissions = new Permissions(); - - for (Permission sandboxPermission : sandboxPermissions) { - permissions.add(sandboxPermission); - } - - if (grantAwtPermissions) { - permissions.add(new AWTPermission("showWindowWithoutWarningBanner")); - } - if (file == null) { - throw new NullJnlpFileException("Can not return sandbox permissions, file is null"); - } - if (file.isApplication()) { - for (Permission jnlpRIAPermission : jnlpRIAPermissions) { - permissions.add(jnlpRIAPermission); - } - } - - if (downloadHost != null && downloadHost.getHost().length() > 0) { - permissions.add(new SocketPermission(UrlUtils.getHostAndPort(downloadHost), - "connect, accept")); - } - - final Collection urlPermissions = getUrlPermissions(); - for (final Permission permission : urlPermissions) { - permissions.add(permission); - } - - return permissions; - } - - private Set getUrlPermissions() { - if (urlPermissionClass == null || urlPermissionConstructor == null) { - return Collections.emptySet(); - } - final Set permissions = new HashSet<>(); - for (final JARDesc jar : file.getResources().getJARs()) { - try { - // Allow applets all HTTP methods (ex POST, GET) with any request headers - // on resources anywhere recursively in or below the applet codebase, only on - // default ports and ports explicitly specified in resource locations - final URI resourceLocation = jar.getLocation().toURI().normalize(); - final URI host = getHost(resourceLocation); - final String hostUriString = host.toString(); - final String urlPermissionUrlString = appendRecursiveSubdirToCodebaseHostString(hostUriString); - final Permission p = urlPermissionConstructor.newInstance(urlPermissionUrlString); - permissions.add(p); - } catch (final ReflectiveOperationException e) { - LOG.error("Exception while attempting to reflectively generate a URLPermission, probably not running on Java 8+?", e); - } catch (final URISyntaxException e) { - LOG.error("Could not determine codebase host for resource at " + jar.getLocation() + " while generating URLPermissions", e); - } - } - try { - final URI codebase = file.getNotNullProbableCodeBase().toURI().normalize(); - final URI host = getHost(codebase); - final String codebaseHostUriString = host.toString(); - final String urlPermissionUrlString = appendRecursiveSubdirToCodebaseHostString(codebaseHostUriString); - final Permission p = urlPermissionConstructor.newInstance(urlPermissionUrlString); - permissions.add(p); - } catch (final ReflectiveOperationException e) { - LOG.error("Exception while attempting to reflectively generate a URLPermission, probably not running on Java 8+?", e); - } catch (final URISyntaxException e) { - LOG.error("Could not determine codebase host for codebase " + file.getCodeBase() + " while generating URLPermissions", e); - } - return permissions; - } - - /** - * Gets the host domain part of an applet's codebase. Removes path, query, and fragment, but preserves scheme, - * user info, and host. The port used is overridden with the specified port. - * @param codebase the applet codebase URL - * @param port - * @return the host domain of the codebase - * @throws URISyntaxException - */ - static URI getHostWithSpecifiedPort(final URI codebase, final int port) throws URISyntaxException { - Objects.requireNonNull(codebase); - return new URI(codebase.getScheme(), codebase.getUserInfo(), codebase.getHost(), port, null, null, null); - } - - /** - * Gets the host domain part of an applet's codebase. Removes path, query, and fragment, but preserves scheme, - * user info, host, and port. - * @param codebase the applet codebase URL - * @return the host domain of the codebase - * @throws URISyntaxException - */ - static URI getHost(final URI codebase) throws URISyntaxException { - Objects.requireNonNull(codebase); - return getHostWithSpecifiedPort(codebase, codebase.getPort()); + public SecurityDesc(final ApplicationEnvironment applicationEnvironment) { + this.applicationEnvironment = applicationEnvironment; } - /** - * Appends a recursive access marker to a codebase host, for granting Java 8 URLPermissions which are no - * more restrictive than the existing SocketPermissions - * See http://docs.oracle.com/javase/8/docs/api/java/net/URLPermission.html - * @param codebaseHost the applet's codebase's host domain URL as a String. Expected to be formatted as eg - * "http://example.com:8080" or "http://example.com/" - * @return the resulting String eg "http://example.com:8080/- - */ - static String appendRecursiveSubdirToCodebaseHostString(final String codebaseHost) { - Objects.requireNonNull(codebaseHost); - String result = codebaseHost; - while (result.endsWith("/")) { - result = result.substring(0, result.length() - 1); - } - // See http://docs.oracle.com/javase/8/docs/api/java/net/URLPermission.html - result = result + "/-"; // allow access to any resources recursively on the host domain - return result; + public ApplicationEnvironment getApplicationEnvironment() { + return applicationEnvironment; } - - /** - * @return all the names of the basic JNLP system properties accessible by RIAs - */ - public static String[] getJnlpRIAPermissions() { - String[] jnlpPermissions = new String[jnlpRIAPermissions.length]; - - for (int i = 0; i < jnlpRIAPermissions.length; i++) - jnlpPermissions[i] = jnlpRIAPermissions[i].getName(); - - return jnlpPermissions; - } - } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesChecker.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesChecker.java index ba5f5e4e9..028a630f7 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesChecker.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesChecker.java @@ -34,27 +34,24 @@ */ package net.adoptopenjdk.icedteaweb.manifest; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletStartupSecuritySettings; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.LaunchException; import net.sourceforge.jnlp.config.ConfigurationConstants; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.ApplicationManager; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader.SigningState; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; import net.sourceforge.jnlp.util.ClasspathMatcher.ClasspathMatchers; import net.sourceforge.jnlp.util.UrlUtils; -import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; @@ -64,24 +61,23 @@ import java.util.Set; import java.util.stream.Collectors; -import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.ALL; +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.J2EE; +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.SANDBOX; import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; public class ManifestAttributesChecker { - private final static Logger LOG = LoggerFactory.getLogger(ManifestAttributesChecker.class); + private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributesChecker.class); - private final SecurityDesc security; private final JNLPFile file; - private final SigningState signing; - private final SecurityDelegate securityDelegate; + private final boolean isFullySigned; + private final ManifestAttributesReader reader; - public ManifestAttributesChecker(final SecurityDesc security, final JNLPFile file, - final SigningState signing, final SecurityDelegate securityDelegate) { - this.security = security; + public ManifestAttributesChecker(final JNLPFile file, boolean isFullySigned, ManifestAttributesReader reader) { this.file = file; - this.signing = signing; - this.securityDelegate = securityDelegate; + this.isFullySigned = isFullySigned; + this.reader = reader; } public enum MANIFEST_ATTRIBUTES_CHECK { @@ -155,7 +151,7 @@ public static List getAttributesCheck() { * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#entry_pt */ private void checkEntryPoint() throws LaunchException { - if (signing == SigningState.NONE) { + if (!isFullySigned) { return; /*when app is not signed at all, then skip this check*/ } if (file.getEntryPointDesc() == null) { @@ -166,7 +162,7 @@ private void checkEntryPoint() throws LaunchException { LOG.debug("Entry-Point can not be checked now, because of unknown main class."); return; } - final String[] eps = file.getManifestAttributesReader().getEntryPoints(); + final String[] eps = reader.getEntryPoints(); String mainClass = file.getEntryPointDesc().getMainClass(); if (eps == null) { LOG.debug("Entry-Point manifest attribute for yours '{}' not found. Continuing.", mainClass); @@ -178,14 +174,14 @@ private void checkEntryPoint() throws LaunchException { return; } } - throw new LaunchException("None of the entry points specified: '" + file.getManifestAttributesReader().getEntryPoint() + "' matched the main class " + mainClass + " and applet is signed. This is a security error and the app will not be launched."); + throw new LaunchException("None of the entry points specified: '" + reader.getEntryPoint() + "' matched the main class " + mainClass + " and applet is signed. This is a security error and the app will not be launched."); } /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#trusted_only */ private void checkTrustedOnlyAttribute() throws LaunchException { - final ManifestBoolean trustedOnly = file.getManifestAttributesReader().isTrustedOnly(); + final ManifestBoolean trustedOnly = reader.isTrustedOnly(); if (trustedOnly == ManifestBoolean.UNDEFINED) { LOG.debug("Trusted Only manifest attribute not found. Continuing."); return; @@ -196,35 +192,31 @@ private void checkTrustedOnlyAttribute() throws LaunchException { return; } - final Object desc = security.getSecurityType(); + //final Object desc = security.getSecurityType(); + final ApplicationEnvironment applicationEnvironment = getApplicationEnvironment(); final String securityType; - if (desc == null) { + if (applicationEnvironment == null) { securityType = "Not Specified"; - } else if (desc.equals(SecurityDesc.ALL_PERMISSIONS)) { + } else if (applicationEnvironment == ALL) { securityType = "All-Permission"; - } else if (desc.equals(SecurityDesc.SANDBOX_PERMISSIONS)) { + } else if (applicationEnvironment == SANDBOX) { securityType = "Sandbox"; - } else if (desc.equals(SecurityDesc.J2EE_PERMISSIONS)) { + } else if (applicationEnvironment == J2EE) { securityType = "J2EE"; } else { securityType = "Unknown"; } - final boolean isFullySigned = signing == SigningState.FULL; - final boolean isSandboxed = securityDelegate.getRunInSandbox(); - final boolean requestsCorrectPermissions = (isFullySigned && SecurityDesc.ALL_PERMISSIONS.equals(desc)) - || (isSandboxed && SecurityDesc.SANDBOX_PERMISSIONS.equals(desc)); + final boolean requestsSpecialPermission = applicationEnvironment == ALL || applicationEnvironment == J2EE; final String signedMsg; - if (isFullySigned && !isSandboxed) { - signedMsg = R("STOAsignedMsgFully"); - } else if (isFullySigned) { - signedMsg = R("STOAsignedMsgAndSandbox"); + if (isFullySigned) { + signedMsg = "The application is fully signed"; } else { - signedMsg = R("STOAsignedMsgPartiall"); + signedMsg = "The application is not fully signed"; } LOG.debug("Trusted Only manifest attribute is \"true\". {} and requests permission level: {}", signedMsg, securityType); - if (!(isFullySigned && requestsCorrectPermissions)) { + if (requestsSpecialPermission && !isFullySigned) { throw new LaunchException("This application specifies Trusted-only as True in its Manifest. " + signedMsg + " and requests permission level: " + securityType + ". This is not allowed."); } } @@ -232,19 +224,19 @@ private void checkTrustedOnlyAttribute() throws LaunchException { /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/manifest.html#codebase */ - private void checkCodebaseAttribute() throws LaunchException { + private void checkCodebaseAttribute() { if (file.getCodeBase() == null || file.getCodeBase().getProtocol().equals(FILE_PROTOCOL)) { LOG.warn("The application is a local file. Codebase validation is disabled. See: http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html for details."); return; } - final Object securityType = security.getSecurityType(); + final ApplicationEnvironment applicationEnvironment = getApplicationEnvironment(); final URL codebase = UrlUtils.guessCodeBase(file); - final ClasspathMatchers codebaseAtt = file.getManifestAttributesReader().getCodebase(); + final ClasspathMatchers codebaseAtt = reader.getCodebase(); if (codebaseAtt == null) { LOG.warn("This application does not specify a Codebase in its manifest. Please verify with the applet''s vendor. Continuing. See: http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html for details."); return; } - if (securityType.equals(SecurityDesc.SANDBOX_PERMISSIONS)) { + if (applicationEnvironment == SANDBOX) { if (codebaseAtt.matches(codebase)) { LOG.info("Codebase matches codebase manifest attribute, but application is unsigned. Continuing. See: http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html for details."); } else { @@ -264,7 +256,7 @@ private void checkCodebaseAttribute() throws LaunchException { * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#permissions */ private void checkPermissionsAttribute() throws LaunchException { - if (securityDelegate.getRunInSandbox()) { + if (getApplicationEnvironment() == SANDBOX) { LOG.warn("The 'Permissions' attribute of this application is '{}'. You have chosen the Sandbox run option, which overrides the Permissions manifest attribute, or the applet has already been automatically sandboxed.", permissionsToString()); return; } @@ -280,7 +272,7 @@ private void checkPermissionsAttribute() throws LaunchException { throw new LaunchException("Your Extended applets security is at 'Very high', and this application is missing the 'permissions' attribute in manifest. This is fatal"); } if (itwSecurityLevel == AppletSecurityLevel.ASK_UNSIGNED) { - final boolean userApproved = SecurityDialogs.showMissingPermissionsAttributeDialogue(file); + final boolean userApproved = Dialogs.showMissingPermissionsAttributeDialogue(file); if (!userApproved) { throw new LaunchException("Your Extended applets security is at 'high' and this application is missing the 'permissions' attribute in manifest. And you have refused to run it."); } else { @@ -290,35 +282,30 @@ private void checkPermissionsAttribute() throws LaunchException { return; } - final AppletPermissionLevel requestedPermissionLevel = file.getAppletPermissionLevel(); - validateRequestedPermissionLevelMatchesManifestPermissions(requestedPermissionLevel, sandboxForced); - if (isNoneOrDefault(requestedPermissionLevel)) { - if (sandboxForced == ManifestBoolean.TRUE && signing != SigningState.NONE) { + final ApplicationEnvironment requestedEnvironment = file.getApplicationEnvironment(); + validateRequestedEnvironmentMatchesManifestPermissions(requestedEnvironment, sandboxForced); + if (requestedEnvironment == SANDBOX) { + if (sandboxForced == ManifestBoolean.TRUE && isFullySigned) { LOG.warn("The 'permissions' attribute is '{}' and the applet is signed. Forcing sandbox.", permissionsToString()); - securityDelegate.setRunInSandbox(); + getApplicationInstance().setApplicationEnvironment(SANDBOX); } - if (sandboxForced == ManifestBoolean.FALSE && signing == SigningState.NONE) { + if (sandboxForced == ManifestBoolean.FALSE && !isFullySigned) { LOG.warn("The 'permissions' attribute is '{}' and the applet is unsigned. Forcing sandbox.", permissionsToString()); - securityDelegate.setRunInSandbox(); + getApplicationInstance().setApplicationEnvironment(SANDBOX); } } } private static boolean isLowSecurity() { - return AppletStartupSecuritySettings.getInstance().getSecurityLevel().equals(AppletSecurityLevel.ALLOW_UNSIGNED); + return AppletStartupSecuritySettings.getInstance().getSecurityLevel() == AppletSecurityLevel.ALLOW_UNSIGNED; } - private static boolean isNoneOrDefault(final AppletPermissionLevel requested) { - return requested == AppletPermissionLevel.NONE || requested == AppletPermissionLevel.DEFAULT; - } - - private void validateRequestedPermissionLevelMatchesManifestPermissions(final AppletPermissionLevel requested, final ManifestBoolean sandboxForced) throws LaunchException { - if (requested == AppletPermissionLevel.ALL && sandboxForced != ManifestBoolean.FALSE) { - throw new LaunchException("The 'permissions' attribute is '" + permissionsToString() + "' but the applet requested " + requested + ". This is fatal"); - } + private void validateRequestedEnvironmentMatchesManifestPermissions(final ApplicationEnvironment requested, final ManifestBoolean sandboxForced) throws LaunchException { - if (requested == AppletPermissionLevel.SANDBOX && sandboxForced != ManifestBoolean.TRUE) { - throw new LaunchException("The 'permissions' attribute is '" + permissionsToString() + "' but the applet requested " + requested + ". This is fatal"); + if ((requested != SANDBOX && sandboxForced != ManifestBoolean.FALSE) || + (requested == SANDBOX && sandboxForced != ManifestBoolean.TRUE)) { + throw new LaunchException( + String.format("The 'permissions' attribute in %s is '%s' but the application requested %s. This is fatal.", reader.getJarName(), permissionsToString(), requested)); } } @@ -329,7 +316,7 @@ private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchEx //cases final Map> usedUrls = new HashMap<>(); final URL sourceLocation = file.getSourceLocation(); - final ResourcesDesc[] resourcesDescs = file.getResourcesDescs(); + final List resourcesDescs = file.getResourcesDescs(); if (sourceLocation != null) { final URL urlWithoutFileName = UrlUtils.removeFileName(sourceLocation); usedUrls.computeIfAbsent(urlWithoutFileName, url -> new HashSet<>()).add(sourceLocation); @@ -385,9 +372,9 @@ private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchEx } final ClasspathMatchers att; - if (signing != SigningState.NONE) { + if (isFullySigned) { // we only consider values in manifest for signed apps (as they may be faked) - att = file.getManifestAttributesReader().getApplicationLibraryAllowableCodebase(); + att = reader.getApplicationLibraryAllowableCodebase(); } else { att = null; } @@ -399,7 +386,7 @@ private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchEx notOkResources.forEach(url -> LOG.warn("The resource '{}' is not from codebase '{}'", url, codebase)); if (att == null) { - final boolean userApproved = SecurityDialogs.showMissingALACAttributePanel(file, codebase, notOkResources); + final boolean userApproved = Dialogs.showMissingALACAttributePanel(file, codebase, notOkResources); if (!userApproved) { throw new LaunchException("The application uses non-codebase resources, has no Application-Library-Allowable-Codebase Attribute, and was blocked from running by the user"); } else { @@ -416,7 +403,7 @@ private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchEx } } - final boolean userApproved = isLowSecurity() || SecurityDialogs.showMatchingALACAttributePanel(file, codebase, notOkResources); + final boolean userApproved = isLowSecurity() || Dialogs.showMatchingALACAttributePanel(file, codebase, notOkResources); if (!userApproved) { throw new LaunchException("The application uses non-codebase resources, which do match its Application-Library-Allowable-Codebase Attribute, but was blocked from running by the user."); } else { @@ -424,35 +411,13 @@ private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchEx } } - //package private for testing - //not perfect but ok for use case - static URL stripDocbase(URL documentBase) { - String s = documentBase.toExternalForm(); - if (s.endsWith("/") || s.endsWith("\\")) { - return documentBase; - } - int i1 = s.lastIndexOf("/"); - int i2 = s.lastIndexOf("\\"); - int i = Math.max(i1, i2); - if (i <= 8 || i >= s.length()) { - return documentBase; - } - s = s.substring(0, i+1); - try { - documentBase = new URL(s); - } catch (MalformedURLException ex) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, ex); - } - return documentBase; - } - private String permissionsToString() { - final String value = file.getManifestAttributesReader().getPermissions(); + final String value = reader.getPermissions(); if (value == null) { return "Not defined"; - } else if (value.trim().equalsIgnoreCase(AppletPermissionLevel.SANDBOX.getValue())) { + } else if (value.trim().equalsIgnoreCase("sandbox")) { return value.trim(); - } else if (value.trim().equalsIgnoreCase(AppletPermissionLevel.ALL.getValue())) { + } else if (value.trim().equalsIgnoreCase("all-permissions")) { return value.trim(); } else { return "illegal"; @@ -460,19 +425,27 @@ private String permissionsToString() { } private ManifestBoolean isSandboxForced() { - final String permissionLevel = file.getManifestAttributesReader().getPermissions(); + final String permissionLevel = reader.getPermissions(); if (permissionLevel == null) { return ManifestBoolean.UNDEFINED; - } else if (permissionLevel.trim().equalsIgnoreCase(AppletPermissionLevel.SANDBOX.getValue())) { + } else if (permissionLevel.trim().equalsIgnoreCase("sandbox")) { return ManifestBoolean.TRUE; - } else if (permissionLevel.trim().equalsIgnoreCase(AppletPermissionLevel.ALL.getValue())) { + } else if (permissionLevel.trim().equalsIgnoreCase("all-permissions")) { return ManifestBoolean.FALSE; } else { throw new IllegalArgumentException( String.format("Unknown value of %s attribute %s. Expected %s or %s", ManifestAttributes.PERMISSIONS.toString(), permissionLevel, - AppletPermissionLevel.SANDBOX.getValue(), AppletPermissionLevel.ALL.getValue()) + "sandbox", "all-permissions") ); } } + + private ApplicationEnvironment getApplicationEnvironment() { + return getApplicationInstance().getApplicationEnvironment(); + } + + private ApplicationInstance getApplicationInstance() { + return ApplicationManager.getApplication(file).orElseThrow(() -> new IllegalStateException("could not load application instance for jnlp")); + } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesReader.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesReader.java index 15bddf576..4b8cece80 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesReader.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesReader.java @@ -17,23 +17,11 @@ package net.adoptopenjdk.icedteaweb.manifest; import net.adoptopenjdk.icedteaweb.Assert; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; import net.sourceforge.jnlp.util.ClasspathMatcher; import net.sourceforge.jnlp.util.JarFile; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import java.util.jar.Attributes.Name; import java.util.jar.Manifest; @@ -44,43 +32,31 @@ * on the security and configuration attributes stored in the JAR file manifest. */ public class ManifestAttributesReader { - private final static Logger LOG = LoggerFactory.getLogger(ManifestAttributesReader.class); + private final Manifest manifest; + private final String jarFilename; - private final JNLPFile jnlpFile; - private JNLPClassLoader loader; - - public ManifestAttributesReader(final JNLPFile jnlpFile) { - this.jnlpFile = jnlpFile; - } - - public void setLoader(JNLPClassLoader loader) { - this.loader = loader; - } - - public boolean isLoader() { - return loader != null; + public ManifestAttributesReader(File jarFile) { + this.jarFilename = jarFile.getName(); + this.manifest = getManifest(Assert.requireNonNull(jarFile, "jarFile")); } - /** - * main class can be defined outside of manifest. - * This method is mostly for completeness - * @return main-class as it is specified in application - */ - public String getMainClass(){ - if (loader == null) { - LOG.debug("Jars not ready to provide main class"); - return null; + private Manifest getManifest(File jarFile) { + try (JarFile mainJar = new JarFile(jarFile)) { + Manifest manifest = mainJar.getManifest(); + if (manifest != null && manifest.getMainAttributes() != null) { + return manifest; + } + } catch (IOException ignored) { } - return loader.getMainClass(); + return new Manifest(); } /** * The raw string representation (fully qualified class names separated by a space) of the * Entry-Point manifest attribute value that can be used as entry point for the RIA. * - * @see #getEntryPoints() for a tokenized representation of the entry points - * * @return the Entry-Point manifest attribute value + * @see #getEntryPoints() for a tokenized representation of the entry points */ public String getEntryPoint() { return getAttribute(ManifestAttributes.ENTRY_POINT.toString()); @@ -187,6 +163,16 @@ public String getPermissions() { return getAttribute(ManifestAttributes.PERMISSIONS.toString()); } + /** + * Gets the name of the main method as specified in the manifest used for launching applications + * packaged in JAR files. + * + * @return the main class name, null if there isn't one of if there was an error + */ + public String getMainClass() { + return getAttribute(Name.MAIN_CLASS); + } + /** * Get the manifest attribute value. */ @@ -198,14 +184,10 @@ private String getAttribute(final String name) { * Returns the value of the specified manifest attribute name. * * @param name name of the manifest attribute to find in application - * @return plain attribute value + * @return plain attribute value */ public String getAttribute(final Name name) { - if (loader == null) { - LOG.debug("Jars not ready to provide attribute {}", name); - return null; - } - return getAttributeFromJars(name, Arrays.asList(jnlpFile.getResources().getJARs()), loader.getTracker()); + return manifest.getMainAttributes().getValue(name); } private ManifestBoolean getBooleanAttribute(final String name) throws IllegalArgumentException { @@ -216,7 +198,7 @@ private ManifestBoolean getBooleanAttribute(final String name) throws IllegalArg value = value.toLowerCase().trim(); switch (value) { case "true": - return ManifestBoolean.TRUE; + return ManifestBoolean.TRUE; case "false": return ManifestBoolean.FALSE; default: @@ -225,131 +207,7 @@ private ManifestBoolean getBooleanAttribute(final String name) throws IllegalArg } } - /** - * Returns the value of the specified manifest attribute name. To do so, the given jar files - * are consulted in the following order: "main" jar in the given list, first jar in the given list, - * all jars in the given list. - * - * @param name attribute to be found - * @param jars Jars that are checked to see if they contain the main class - * @param tracker tracker to use for the jar file lookup - * @return the attribute value, null if no attribute could be found for some reason - */ - public static String getAttributeFromJars(final Name name, final List jars, final ResourceTracker tracker) { - if (jars.isEmpty()) { - return null; - } - - // Check main jar - final JARDesc mainJarDesc = ResourcesDesc.getMainJAR(jars); - if (mainJarDesc == null) { - return null; - } - String result = getAttributeFromJar(name, mainJarDesc.getLocation(), tracker); - if (result != null) { - return result; - } - - // Check first jar - JARDesc firstJarDesc = jars.get(0); - result = getAttributeFromJar(name, firstJarDesc.getLocation(), tracker); - - if (result != null) { - return result; - } - - // Still not found? Iterate and set if only 1 was found - for (JARDesc jarDesc : jars) { - final String attributeInThisJar = getAttributeFromJar(name, jarDesc.getLocation(), tracker); - if (attributeInThisJar != null) { - if (result == null) { // first main class - result = attributeInThisJar; - } else { // There is more than one main class. Set to null and break. - result = null; - break; - } - } - } - return result; - } - - /** - * Returns the value of the specified manifest attribute name, or null if the JAR referenced by the given location URL - * does not contain a manifest or the attribute could not not be found in the manifest. - * - * @param name name of the attribute to find - * @param location The JAR location - * @param tracker resource tracker to use for the jar file lookup - * - * @return the attribute value, null if no attribute could be found for some reason - */ - public static String getAttributeFromJar(final Name name, final URL location, final ResourceTracker tracker) { - Assert.requireNonNull(name, "name"); - Assert.requireNonNull(location, "location"); - Assert.requireNonNull(tracker, "tracker"); - - final File file = tracker.getCacheFile(location); - - if (file != null) { - try (JarFile mainJar = new JarFile(file)) { - final Manifest manifest = mainJar.getManifest(); - if (manifest == null || manifest.getMainAttributes() == null) { - //yes, jars without manifest exists - return null; - } - return manifest.getMainAttributes().getValue(name); - } catch (IOException ioe) { - return null; - } - } - return null; - } - - /** - * Gets the name of the main method as specified in the manifest used for launching applications - * packaged in JAR files. - * - * @param location The JAR location - * @return the main class name, null if there isn't one of if there was an error - */ - public String getMainClass(final URL location, final ResourceTracker resourceTracker) { - return getAttributeFromJar(Name.MAIN_CLASS, location, resourceTracker); - } - - /** - * Returns a set of paths that indicate the Class-Path entries in the manifest file. - * The paths are rooted in the same directory as the originalJarPath. - * - * @param manifest the manifest - * @param originalJarPathLocation the remote/original path of the jar containing the manifest - * @return a Set of String where each string is a path to the jar on the original jar's classpath. - */ - public static Set getClassPaths(final Manifest manifest, final URL originalJarPathLocation) { - final Set result = new HashSet<>(); - if (manifest != null) { - // extract the Class-Path entries from the manifest and split them - final String classpath = manifest.getMainAttributes().getValue(Name.CLASS_PATH.toString()); - if (classpath == null || classpath.trim().length() == 0) { - return result; - } - final String[] paths = classpath.split(" +"); - final String originalJarPath = originalJarPathLocation.getPath(); - for (String path : paths) { - if (path.trim().length() == 0) { - continue; - } - // we want to search for jars in the same subdir on the server - // as the original jar that contains the manifest file, so find - // out its subdirectory and use that as the dir - String dir = ""; - int lastSlash = originalJarPath.lastIndexOf("/"); - if (lastSlash != -1) { - dir = originalJarPath.substring(0, lastSlash + 1); - } - final String fullPath = dir + path; - result.add(fullPath); - } - } - return result; + public String getJarName() { + return jarFilename; } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/proxy/ie/RegistryQuery.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/proxy/ie/RegistryQuery.java index 47009585d..3504aae3d 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/proxy/ie/RegistryQuery.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/proxy/ie/RegistryQuery.java @@ -8,7 +8,6 @@ import java.util.Scanner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Function; import java.util.stream.Collectors; @@ -58,21 +57,14 @@ private static RegistryValue parseSingleLine(final String line) { } private static Future> getLines(final InputStream src) { - final CompletableFuture> result = new CompletableFuture<>(); - - Executors.newSingleThreadExecutor().execute(() -> { - try { - final List lines = new ArrayList<>(); - final Scanner sc = new Scanner(src); - while (sc.hasNextLine()) { - lines.add(sc.nextLine()); - } - result.complete(lines); - } catch (final Exception e) { - result.completeExceptionally(e); + return CompletableFuture.supplyAsync(() -> { + final List lines = new ArrayList<>(); + final Scanner sc = new Scanner(src); + while (sc.hasNextLine()) { + lines.add(sc.nextLine()); } + return lines; }); - return result; } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTracker.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTracker.java new file mode 100644 index 000000000..f546d4915 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTracker.java @@ -0,0 +1,402 @@ +// 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.adoptopenjdk.icedteaweb.resources; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionId; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.CachedDaemonThreadPoolProvider.DaemonThreadFactory; +import net.sourceforge.jnlp.DownloadOptions; +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.config.ConfigurationConstants; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.UrlUtils; + +import javax.jnlp.DownloadServiceListener; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import static net.adoptopenjdk.icedteaweb.resources.Resource.Status.ERROR; +import static net.adoptopenjdk.icedteaweb.resources.Resource.createResource; +import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; +import static net.sourceforge.jnlp.util.UrlUtils.normalizeUrlQuietly; + +/** + * This class tracks the downloading of various resources of a + * JNLP file to local files in the cache. It can be used to + * download icons, jnlp and extension files, jars, and jardiff + * files using the version based protocol or any file using the + * basic download protocol (jardiff and version not implemented + * yet). + *

    + * The resource tracker can be configured to prefetch resources, + * which are downloaded in the order added to the media + * tracker. + *

    + *

    + * Multiple threads are used to download and cache resources that + * are actively being waited for (blocking a caller) or those that + * have been started downloading by calling the startDownload + * method. Resources that are prefetched are downloaded one at a + * time and only if no other trackers have requested downloads. + * This allows the tracker to start downloading many items without + * using many system resources, but still quickly download items + * as needed. + *

    + * + * @author Jon A. Maxwell (JAM) - initial author + * @version $Revision: 1.22 $ + */ +public class DefaultResourceTracker implements ResourceTracker { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultResourceTracker.class); + + // todo: use event listener arrays instead of lists + + // todo: see if there is a way to set the socket options just + // for use by the tracker so checks for updates don't hang for + // a long time. + + // todo: ability to restart/retry a hung download + + // todo: move resource downloading/processing code into Resource + // class, threading stays in ResourceTracker + + // todo: get status method? and some way to convey error status + // to the caller. + + // todo: might make a tracker be able to download more than one + // version of a resource, but probably not very useful. + + /** + * the resources known about by this resource tracker + */ + private final Map resources = new HashMap<>(); + + /** + * whether to download parts before requested + */ + private final boolean prefetch; + + private final DownloadOptions downloadOptions; + private final UpdatePolicy updatePolicy; + + /** + * Creates a resource tracker that does not prefetch resources. + */ + public DefaultResourceTracker() { + this(false); + } + + /** + * Creates a resource tracker that does not prefetch resources. + */ + public DefaultResourceTracker(boolean prefetch) { + this(prefetch, DownloadOptions.NONE, UpdatePolicy.ALWAYS); + } + + /** + * Creates a resource tracker. + * + * @param prefetch whether to download resources before requested. + */ + public DefaultResourceTracker(boolean prefetch, DownloadOptions downloadOptions, UpdatePolicy updatePolicy) { + this.prefetch = prefetch; + this.downloadOptions = Assert.requireNonNull(downloadOptions, "downloadOptions"); + this.updatePolicy = Assert.requireNonNull(updatePolicy, "updatePolicy"); + } + + public void addResource(URL location, final VersionId version) { + final VersionString versionString = version != null ? version.asVersionString() : null; + addResource(location, versionString, updatePolicy); + } + + public void addResource(URL location, final VersionString version) { + addResource(location, version, updatePolicy); + } + + /** + * Add a resource identified by the specified location and + * version. The tracker only downloads one version of a given + * resource per instance (ie cannot download both versions 1 and + * 2 of a resource in the same tracker). + * + * @param location the location of the resource + * @param version the resource version + * @param updatePolicy whether to check for updates if already in cache + */ + public void addResource(URL location, final VersionString version, final UpdatePolicy updatePolicy) { + Assert.requireNonNull(location, "location"); + LOG.debug("Will add resource at location '{}'", location); + + final URL normalizedLocation = normalizeUrlQuietly(location); + final Resource resource = createResource(normalizedLocation, version, downloadOptions, updatePolicy); + LOG.debug("Will add resource '{}'", resource.getSimpleName()); + if (addToResources(resource)) { + startDownloadingIfPrefetch(resource); + } + } + + /** + * @return {@code true} if no resource with the given URL is currently tracked. + */ + private boolean addToResources(Resource resource) { + synchronized (resources) { + final Resource existingResource = resources.get(resource.getLocation()); + + if (existingResource == null) { + resources.put(resource.getLocation(), resource); + } else { + final VersionString newVersion = resource.getRequestVersion(); + final VersionString existingVersion = existingResource.getRequestVersion(); + if (!Objects.equals(existingVersion, newVersion)) { + throw new IllegalStateException("Found two resources with location '" + resource.getLocation() + + "' but different versions '" + newVersion + "' - '" + existingVersion + "'"); + } + } + + return existingResource == null; + } + } + + private void startDownloadingIfPrefetch(Resource resource) { + if (prefetch && !resource.isComplete() && !resource.isBeingProcessed()) { + triggerDownloadFor(resource, Executors.newSingleThreadExecutor(new DaemonThreadFactory())); + } + } + + /** + * Returns a URL pointing to the cached location of the + * resource, or the resource itself if it is a non-cacheable + * resource. + *

    + * If the resource has not downloaded yet, the method will block + * until it has been transferred to the cache. + *

    + * + * @param location the resource location + * @return the resource, or null if it could not be downloaded + * @throws IllegalResourceDescriptorException if the resource is not being tracked + * @see CacheUtil#isCacheable + * @deprecated + */ + @Deprecated + public URL getCacheURL(URL location) { + final File f = getCacheFile(location); + if (f != null) { + try { + return f.toURI().toURL(); + } catch (MalformedURLException ex) { + LOG.error("Invalid URL {} - {}", f.toURI(), ex.getMessage()); + } + } + + return location; + } + + /** + * Returns a file containing the downloaded resource. If the + * resource is non-cacheable then null is returned unless the + * resource is a local file (the original file is returned). + *

    + * If the resource has not downloaded yet, the method will block + * until it has been transferred to the cache. + *

    + * + * @param location the resource location + * @return a local file containing the resource, or null + * @throws IllegalResourceDescriptorException if the resource is not being tracked + * @see CacheUtil#isCacheable + */ + public File getCacheFile(URL location) { + Resource resource = getResource(location); + try { + if (!resource.isComplete()) { + waitForCompletion(resource); + } + } catch (Exception ex) { + LOG.error("Error while fetching resource " + location, ex); + return null; // need an error exception to throw + } + return getCacheFile(resource); + } + + private static File getCacheFile(final Resource resource) { + final URL location = resource.getLocation(); + if (resource.isSet(ERROR)) { + LOG.debug("Error flag set for resource '{}'. Can not return a local file for the resource", resource.getLocation()); + return null; + } + + if (resource.getLocalFile() != null) { + return resource.getLocalFile(); + } + + if (location.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) { + File file = UrlUtils.decodeUrlAsFile(location); + if (file.exists()) { + return file; + } + // try plain, not decoded file now + // sometimes the jnlp app developers are encoding for us + // so we end up encoding already encoded file. See RH1154177 + file = new File(location.getPath()); + if (file.exists()) { + return file; + } + // have it sense to try also filename with whole query here? + // => location.getFile() ? + } + LOG.debug("No local file defined for resource '{}'", resource.getLocation()); + + return null; + } + + + /** + * Returns the number of bytes downloaded for a resource. + * + * @param location the resource location + * @return the number of bytes transferred + * @throws IllegalResourceDescriptorException if the resource is not being tracked + * @deprecated + */ + @Deprecated + public long getAmountRead(URL location) { + // not atomic b/c transferred is a long, but so what (each + // byte atomic? so probably won't affect anything...) + return getResource(location).getTransferred(); + } + + /** + * Returns whether a resource is available for use (ie, can be + * accessed with the getCacheFile method). + * + * @param location the resource location + * @return resource availability + * @throws IllegalResourceDescriptorException if the resource is not being tracked + * @deprecated + */ + @Deprecated + public boolean checkResource(URL location) { + Resource resource = getResource(location); + return resource.isComplete(); + } + + public boolean isResourceAdded(URL location) { + return getOptionalResource(location).isPresent(); + } + + /** + * Returns the number of total size in bytes of a resource, or + * -1 it the size is not known. + * + * @param location the resource location + * @return the number of bytes, or -1 + * @throws IllegalResourceDescriptorException if the resource is not being tracked + * @deprecated + */ + @Deprecated + public long getTotalSize(URL location) { + return getResource(location).getSize(); // atomic + } + + /** + * Return the resource matching the specified URL. + * + * @throws IllegalResourceDescriptorException if the resource is not being tracked + */ + private Resource getResource(URL location) { + return getOptionalResource(location) + .orElseThrow(() -> new IllegalResourceDescriptorException("Location " + location + " does not specify a resource being tracked.")); + } + + private Optional getOptionalResource(URL location) { + final URL normalizedLocation = normalizeUrlQuietly(location); + synchronized (resources) { + return Optional.ofNullable(resources.get(normalizedLocation)); + } + } + + /** + * Wait for some resources. + * + * @param resources the resources to wait for + */ + private void waitForCompletion(Resource... resources) { + if (resources == null || resources.length == 0) { + return; + } + + final int configuredThreadCount = Integer.parseInt(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_PARALLEL_RESOURCE_DOWNLOAD_COUNT)); + final int threadCount = Math.min(configuredThreadCount, resources.length); + final ExecutorService downloadExecutor = Executors.newFixedThreadPool(threadCount, new DaemonThreadFactory()); + try { + final List> futures = Arrays.stream(resources) + .map(r -> triggerDownloadFor(r, downloadExecutor)) + .collect(Collectors.toList()); + + for (Future future : futures) { + future.get(); + } + } catch (final Exception e) { + throw new RuntimeException("Error while waiting for download", e); + } finally { + LOG.debug("Download done. Shutting down executor"); + downloadExecutor.shutdownNow(); + } + } + + private Future triggerDownloadFor(Resource resource, final Executor downloadExecutor) { + return new ResourceHandler(resource).putIntoCache(downloadExecutor); + } + + public void addDownloadListener(final URL resourceUrl, URL[] allResources, final DownloadServiceListener listener) { + final Resource resource = getResource(resourceUrl); + resource.addPropertyChangeListener(Resource.SIZE_PROPERTY, e -> listener.progress(resourceUrl, "version", resource.getTransferred(), resource.getSize(), getPercentageDownloaded(allResources))); + resource.addPropertyChangeListener(Resource.TRANSFERRED_PROPERTY, e -> listener.progress(resourceUrl, "version", resource.getTransferred(), resource.getSize(), getPercentageDownloaded(allResources))); + } + + private int getPercentageDownloaded(URL[] allResources) { + return (int) Arrays.stream(allResources) + .map(this::getResource) + .mapToDouble(r -> { + if (r.isComplete()) { + return 100.0d; + } + if (r.isBeingProcessed() && r.getSize() > 0) { + final double p = (100.0d * r.getTransferred()) / r.getSize(); + return Math.max(0.0d, Math.min(100.0d, p)); + } + return 0.0d; + }).sum() / allResources.length; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerFactory.java new file mode 100644 index 000000000..557af786e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerFactory.java @@ -0,0 +1,9 @@ +package net.adoptopenjdk.icedteaweb.resources; + +import net.sourceforge.jnlp.DownloadOptions; + +public class DefaultResourceTrackerFactory implements ResourceTrackerFactory{ + public ResourceTracker create(boolean prefetch, DownloadOptions downloadOptions, UpdatePolicy updatePolicy) { + return new DefaultResourceTracker(prefetch, downloadOptions, updatePolicy); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/PrioritizedParallelExecutor.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/PrioritizedParallelExecutor.java index 6d0122de9..c2396b9ad 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/PrioritizedParallelExecutor.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/PrioritizedParallelExecutor.java @@ -46,18 +46,16 @@ public Future getSuccessfulResultWithHighestPriority(final List futureResult = new CompletableFuture<>(); final List exceptions = new ArrayList<>(); for (Callable next : callables) { try { - futureResult.complete(executor.submit(next).get()); - return futureResult; + return CompletableFuture.completedFuture(executor.submit(next).get()); } catch (Exception e) { exceptions.add(e); // continue with next callable } } - + final CompletableFuture futureResult = new CompletableFuture<>(); futureResult.completeExceptionally(getFailureReason(exceptions)); return futureResult; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java index b879331cf..fc8af8f4f 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java @@ -16,36 +16,11 @@ package net.adoptopenjdk.icedteaweb.resources; -import net.adoptopenjdk.icedteaweb.Assert; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.resources.CachedDaemonThreadPoolProvider.DaemonThreadFactory; -import net.sourceforge.jnlp.DownloadOptions; import net.sourceforge.jnlp.cache.CacheUtil; -import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.util.UrlUtils; -import javax.jnlp.DownloadServiceListener; import java.io.File; -import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.stream.Collectors; - -import static net.adoptopenjdk.icedteaweb.resources.Resource.Status.ERROR; -import static net.adoptopenjdk.icedteaweb.resources.Resource.createResource; -import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; -import static net.sourceforge.jnlp.util.UrlUtils.normalizeUrlQuietly; /** * This class tracks the downloading of various resources of a @@ -73,68 +48,9 @@ * @author Jon A. Maxwell (JAM) - initial author * @version $Revision: 1.22 $ */ -public class ResourceTracker { - - private static final Logger LOG = LoggerFactory.getLogger(ResourceTracker.class); - - // todo: use event listener arrays instead of lists - - // todo: see if there is a way to set the socket options just - // for use by the tracker so checks for updates don't hang for - // a long time. - - // todo: ability to restart/retry a hung download - - // todo: move resource downloading/processing code into Resource - // class, threading stays in ResourceTracker - - // todo: get status method? and some way to convey error status - // to the caller. - - // todo: might make a tracker be able to download more than one - // version of a resource, but probably not very useful. - - /** - * the resources known about by this resource tracker - */ - private final Map resources = new HashMap<>(); - - /** - * whether to download parts before requested - */ - private final boolean prefetch; - - private final DownloadOptions downloadOptions; - private final UpdatePolicy updatePolicy; - - /** - * Creates a resource tracker that does not prefetch resources. - */ - public ResourceTracker() { - this(false); - } - - /** - * Creates a resource tracker that does not prefetch resources. - */ - public ResourceTracker(boolean prefetch) { - this(prefetch, DownloadOptions.NONE, UpdatePolicy.ALWAYS); - } - - /** - * Creates a resource tracker. - * - * @param prefetch whether to download resources before requested. - */ - public ResourceTracker(boolean prefetch, DownloadOptions downloadOptions, UpdatePolicy updatePolicy) { - this.prefetch = prefetch; - this.downloadOptions = Assert.requireNonNull(downloadOptions, "downloadOptions"); - this.updatePolicy = Assert.requireNonNull(updatePolicy, "updatePolicy"); - } +public interface ResourceTracker { - public void addResource(URL location, final VersionString version) { - addResource(location, version, updatePolicy); - } + void addResource(URL location, final VersionString version); /** * Add a resource identified by the specified location and @@ -146,89 +62,7 @@ public void addResource(URL location, final VersionString version) { * @param version the resource version * @param updatePolicy whether to check for updates if already in cache */ - public void addResource(URL location, final VersionString version, final UpdatePolicy updatePolicy) { - Assert.requireNonNull(location, "location"); - LOG.debug("Will add resource at location '{}'", location); - - - final URL normalizedLocation = normalizeUrlQuietly(location); - final Resource resource = createResource(normalizedLocation, version, downloadOptions, updatePolicy); - LOG.debug("Will add resource '{}'", resource.getSimpleName()); - if (addToResources(resource)) { - startDownloadingIfPrefetch(resource); - } - } - - /** - * @return {@code true} if no resource with the given URL is currently tracked. - */ - private boolean addToResources(Resource resource) { - synchronized (resources) { - final Resource existingResource = resources.get(resource.getLocation()); - - if (existingResource == null) { - resources.put(resource.getLocation(), resource); - } else { - final VersionString newVersion = resource.getRequestVersion(); - final VersionString existingVersion = existingResource.getRequestVersion(); - if (!Objects.equals(existingVersion, newVersion)) { - throw new IllegalStateException("Found two resources with location '" + resource.getLocation() + - "' but different versions '" + newVersion + "' - '" + existingVersion + "'"); - } - } - - return existingResource == null; - } - } - - private void startDownloadingIfPrefetch(Resource resource) { - if (prefetch && !resource.isComplete() && !resource.isBeingProcessed()) { - triggerDownloadFor(resource, Executors.newSingleThreadExecutor(new DaemonThreadFactory())); - } - } - - /** - * Removes a resource from the tracker. This method is useful - * to allow memory to be reclaimed, but calling this method is - * not required as resources are reclaimed when the tracker is - * collected. - * - * @param location location of resource to be removed - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - public void removeResource(URL location) { - synchronized (resources) { - Resource resource = getResource(location); - resources.remove(resource.getLocation()); - } - } - - /** - * Returns a URL pointing to the cached location of the - * resource, or the resource itself if it is a non-cacheable - * resource. - *

    - * If the resource has not downloaded yet, the method will block - * until it has been transferred to the cache. - *

    - * - * @param location the resource location - * @return the resource, or null if it could not be downloaded - * @throws IllegalResourceDescriptorException if the resource is not being tracked - * @see CacheUtil#isCacheable - */ - public URL getCacheURL(URL location) { - final File f = getCacheFile(location); - if (f != null) { - try { - return f.toURI().toURL(); - } catch (MalformedURLException ex) { - LOG.error("Invalid URL {} - {}", f.toURI(), ex.getMessage()); - } - } - - return location; - } + void addResource(URL location, final VersionString version, final UpdatePolicy updatePolicy); /** * Returns a file containing the downloaded resource. If the @@ -244,182 +78,7 @@ public URL getCacheURL(URL location) { * @throws IllegalResourceDescriptorException if the resource is not being tracked * @see CacheUtil#isCacheable */ - public File getCacheFile(URL location) { - Resource resource = getResource(location); - try { - if (!resource.isComplete()) { - waitForCompletion(resource); - } - } catch (Exception ex) { - LOG.error("Error while fetching resource " + location, ex); - return null; // need an error exception to throw - } - return getCacheFile(resource); - } - - private static File getCacheFile(final Resource resource) { - final URL location = resource.getLocation(); - if (resource.isSet(ERROR)) { - LOG.debug("Error flag set for resource '{}'. Can not return a local file for the resource", resource.getLocation()); - return null; - } - - if (resource.getLocalFile() != null) { - return resource.getLocalFile(); - } - - if (location.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) { - File file = UrlUtils.decodeUrlAsFile(location); - if (file.exists()) { - return file; - } - // try plain, not decoded file now - // sometimes the jnlp app developers are encoding for us - // so we end up encoding already encoded file. See RH1154177 - file = new File(location.getPath()); - if (file.exists()) { - return file; - } - // have it sense to try also filename with whole query here? - // => location.getFile() ? - } - LOG.debug("No local file defined for resource '{}'", resource.getLocation()); - - return null; - } - - /** - * Wait for a group of resources to be downloaded and made - * available locally. - * - * @param urls the resources to wait for - * @throws InterruptedException if thread is interrupted - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - public void waitForResources(URL... urls) throws InterruptedException { - waitForCompletion(getResources(urls)); - } - - /** - * Returns the number of bytes downloaded for a resource. - * - * @param location the resource location - * @return the number of bytes transferred - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - public long getAmountRead(URL location) { - // not atomic b/c transferred is a long, but so what (each - // byte atomic? so probably won't affect anything...) - return getResource(location).getTransferred(); - } - - /** - * Returns whether a resource is available for use (ie, can be - * accessed with the getCacheFile method). - * - * @param location the resource location - * @return resource availability - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - public boolean checkResource(URL location) { - Resource resource = getResource(location); - return resource.isComplete(); - } - - /** - * Returns the number of total size in bytes of a resource, or - * -1 it the size is not known. - * - * @param location the resource location - * @return the number of bytes, or -1 - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - public long getTotalSize(URL location) { - return getResource(location).getSize(); // atomic - } - - private Resource[] getResources(URL[] urls) { - Resource[] lresources = new Resource[urls.length]; - - synchronized (resources) { - // keep the lock so getResource doesn't have to acquire it each time - for (int i = 0; i < urls.length; i++) { - lresources[i] = getResource(urls[i]); - } - } - return lresources; - } - - /** - * Return the resource matching the specified URL. - * - * @throws IllegalResourceDescriptorException if the resource is not being tracked - */ - private Resource getResource(URL location) { - final URL normalizedLocation = normalizeUrlQuietly(location); - synchronized (resources) { - final Resource result = resources.get(normalizedLocation); - if (result == null) { - throw new IllegalResourceDescriptorException("Location " + location + " does not specify a resource being tracked."); - } - return result; - } - } - - /** - * Wait for some resources. - * - * @param resources the resources to wait for - * @return {@code true} if the resources were downloaded or had errors, - * {@code false} if the timeout was reached - * @throws InterruptedException if another thread interrupted the wait - */ - private void waitForCompletion(Resource... resources) { - if (resources == null || resources.length == 0) { - return; - } - - final int configuredThreadCount = Integer.parseInt(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_PARALLEL_RESOURCE_DOWNLOAD_COUNT)); - final int threadCount = Math.min(configuredThreadCount, resources.length); - final ExecutorService downloadExecutor = Executors.newFixedThreadPool(threadCount, new DaemonThreadFactory()); - try { - final List> futures = Arrays.asList(resources).stream() - .map(r -> triggerDownloadFor(r, downloadExecutor)) - .collect(Collectors.toList()); - - for (Future future : futures) { - future.get(); - } - } catch (final Exception e) { - throw new RuntimeException("Error while waiting for download", e); - } finally { - LOG.debug("Download done. Shutting down executor"); - downloadExecutor.shutdownNow(); - } - } - - private Future triggerDownloadFor(Resource resource, final Executor downloadExecutor) { - return new ResourceHandler(resource).putIntoCache(downloadExecutor); - } - - public void addDownloadListener(final URL resourceUrl, URL[] allResources, final DownloadServiceListener listener) { - final Resource resource = getResource(resourceUrl); - resource.addPropertyChangeListener(Resource.SIZE_PROPERTY, e -> listener.progress(resourceUrl, "version", resource.getTransferred(), resource.getSize(), getPercentageDownloaded(allResources))); - resource.addPropertyChangeListener(Resource.TRANSFERRED_PROPERTY, e -> listener.progress(resourceUrl, "version", resource.getTransferred(), resource.getSize(), getPercentageDownloaded(allResources))); - } + File getCacheFile(URL location); - private int getPercentageDownloaded(URL[] allResources) { - return (int) Arrays.asList(allResources).stream() - .map(u -> getResource(u)) - .mapToDouble(r -> { - if (r.isComplete()) { - return 100.0d; - } - if (r.isBeingProcessed() && r.getSize() > 0) { - final double p = (100.0d * r.getTransferred()) / r.getSize(); - return Math.max(0.0d, Math.min(100.0d, p)); - } - return 0.0d; - }).sum() / allResources.length; - } + boolean isResourceAdded(URL location); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerFactory.java new file mode 100644 index 000000000..dba23a909 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerFactory.java @@ -0,0 +1,7 @@ +package net.adoptopenjdk.icedteaweb.resources; + +import net.sourceforge.jnlp.DownloadOptions; + +public interface ResourceTrackerFactory { + ResourceTracker create(boolean prefetch, DownloadOptions downloadOptions, UpdatePolicy updatePolicy); +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/downloader/BaseResourceDownloader.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/downloader/BaseResourceDownloader.java index d779da0c4..14f035ae0 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/downloader/BaseResourceDownloader.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/downloader/BaseResourceDownloader.java @@ -91,15 +91,13 @@ private void checkForProxyError() { private CompletableFuture downloadFrom(final URL url) { LOG.debug("Will download in background: {}", url); - final CompletableFuture result = new CompletableFuture<>(); - CachedDaemonThreadPoolProvider.getThreadPool().execute(() -> { + return CompletableFuture.supplyAsync(() -> { try { - result.complete(tryDownloading(url)); - } catch (Exception e) { - result.completeExceptionally(e); + return tryDownloading(url); + } catch (IOException e) { + throw new RuntimeException("Error while downloading " + url, e); } - }); - return result; + }, CachedDaemonThreadPoolProvider.getThreadPool()); } private Resource tryDownloading(final URL downloadFrom) throws IOException { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/initializer/BaseResourceInitializer.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/initializer/BaseResourceInitializer.java index 3184b7e1f..f2ac24143 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/initializer/BaseResourceInitializer.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/initializer/BaseResourceInitializer.java @@ -1,7 +1,7 @@ package net.adoptopenjdk.icedteaweb.resources.initializer; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.InetSecurity511Panel; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.http.HttpMethod; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionId; import net.adoptopenjdk.icedteaweb.logging.Logger; @@ -102,7 +102,7 @@ private UrlRequestResult testUrl(URL url) throws IOException { final UrlRequestResult response = UrlProber.getUrlResponseCodeWithRedirectionResult(url, requestProperties, requestMethod); if (response.getResponseCode() == NETWORK_AUTHENTICATION_REQUIRED && !InetSecurity511Panel.isSkip()) { - boolean result511 = SecurityDialogs.show511Dialogue(resource); + boolean result511 = Dialogs.show511Dialogue(resource); if (!result511) { throw new RuntimeException("Terminated on users request after encountering 'http 511 authentication'."); } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/PermissionsManager.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/PermissionsManager.java new file mode 100644 index 000000000..c908257d8 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/PermissionsManager.java @@ -0,0 +1,394 @@ +package net.adoptopenjdk.icedteaweb.security; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.config.ConfigurationConstants; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.UrlUtils; + +import java.awt.AWTPermission; +import java.io.FilePermission; +import java.lang.reflect.Constructor; +import java.net.SocketPermission; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.URIParameter; +import java.util.Collections; +import java.util.Objects; +import java.util.PropertyPermission; + +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.ARRAY_LEGACY_MERGE_SORT; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_AA_FONT_SETTINGS; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_DISABLE_MIXING; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_ERASE_BACKGROUND_ON_RESIZE; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_NO_ERASE_BACKGROUND; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_SYNC_LWREQUESTS; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.AWT_WINDOW_LOCATION_BY_PLATFORM; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.BROWSER; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.BROWSER_STAR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.EXIT_VM; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.FILE_SEPARATOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.HTTP_AGENT; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.HTTP_KEEP_ALIVE; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_D3D; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_DPI_AWARE; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_NO_DDRAW; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA2D_OPENGL; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVAWS; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_CLASS_VERSION; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_PLUGIN; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_NAME; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_VENDOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_SPEC_VERSION; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VENDOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VENDOR_URL; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JAVA_VERSION; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.JNLP; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.LINE_SEPARATOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_ARCH; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_NAME; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.OS_VERSION; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.PATH_SEPARATOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.STOP_THREAD; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_BOLD_METAL; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_DEFAULT_LF; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_METAL_THEME; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_NO_XP; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.SWING_USE_SYSTEM_FONT; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_NAME; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_SPEC_NAME; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_SPEC_VENDOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_VENDOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.VM_VERSION; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.WEBSTART_JAUTHENTICATOR; +import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.WEBSTART_VERSION; +import static sun.security.util.SecurityConstants.PROPERTY_READ_ACTION; +import static sun.security.util.SecurityConstants.PROPERTY_RW_ACTION; + +public class PermissionsManager { + private static final Logger LOG = LoggerFactory.getLogger(PermissionsManager.class); + + /** + * URLPermission is new in Java 8, so we use reflection to check for it to keep compatibility + * with Java 6/7. If we can't find the class or fail to construct it then we continue as usual + * without. + *

    + * These are saved as fields so that the reflective lookup only needs to be performed once + * when the SecurityDesc is constructed, rather than every time a call is made to + * {@link PermissionsManager#getSandBoxPermissions(JNLPFile)}, which is called frequently. + */ + private static Class urlPermissionClass; + private static Constructor urlPermissionConstructor; + + private static PermissionCollection j2EEPermissions; + private static PermissionCollection jnlpRIAPermissions; + private static PermissionCollection sandboxPermissions; + private static PermissionCollection urlPermissions; + + static { + try { + urlPermissionClass = (Class) Class.forName("java.net.URLPermission"); + urlPermissionConstructor = urlPermissionClass.getDeclaredConstructor(String.class); + } catch (final ReflectiveOperationException | SecurityException e) { + LOG.error("Exception while reflectively finding URLPermission - host is probably not running Java 8+", e); + urlPermissionClass = null; + urlPermissionConstructor = null; + } + } + + // We go by the rules here: + // http://java.sun.com/docs/books/tutorial/deployment/doingMoreWithRIA/properties.html + + // Since this is security sensitive, take a conservative approach: + // Allow only what is specifically allowed, and deny everything else + + /** + * @return a read-only PermissionCollection containing the sandbox permissions + */ + public static PermissionCollection getSandBoxPermissions(final JNLPFile file) { + if (sandboxPermissions == null) { + sandboxPermissions = new Permissions(); + + sandboxPermissions.add(new SocketPermission("localhost:1024-", "listen")); + // sandboxPermissions.add(new SocketPermission("", "connect, accept")); // added by code + sandboxPermissions.add(new PropertyPermission(ARRAY_LEGACY_MERGE_SORT, PROPERTY_RW_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_VENDOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_VENDOR_URL, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_CLASS_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(OS_NAME, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(OS_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(OS_ARCH, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(FILE_SEPARATOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(PATH_SEPARATOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(LINE_SEPARATOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_SPEC_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_SPEC_VENDOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_SPEC_NAME, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(VM_SPEC_VENDOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(VM_SPEC_NAME, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(VM_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(VM_VENDOR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(VM_NAME, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(WEBSTART_VERSION, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVA_PLUGIN, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(JNLP, PROPERTY_RW_ACTION)); + sandboxPermissions.add(new PropertyPermission(JAVAWS, PROPERTY_RW_ACTION)); + sandboxPermissions.add(new PropertyPermission(BROWSER, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new PropertyPermission(BROWSER_STAR, PROPERTY_READ_ACTION)); + sandboxPermissions.add(new RuntimePermission(EXIT_VM)); + sandboxPermissions.add(new RuntimePermission(STOP_THREAD)); + // disabled because we can't at this time prevent an + // application from accessing other applications' event + // queues, or even prevent access to security dialog queues. + // + // sandboxPermissions.add(new AWTPermission("accessEventQueue")); + + if (shouldAddShowWindowWithoutWarningBannerAwtPermission()) { + sandboxPermissions.add(new AWTPermission("showWindowWithoutWarningBanner")); + } + + if (file.isApplication()) { + Collections.list(getJnlpRiaPermissions().elements()).forEach(sandboxPermissions::add); + } + URL downloadHost = UrlUtils.guessCodeBase(file); + if (downloadHost != null && downloadHost.getHost().length() > 0) { + sandboxPermissions.add(new SocketPermission(UrlUtils.getHostAndPort(downloadHost), "connect, accept")); + } + + Collections.list(getUrlPermissions(file).elements()).forEach(sandboxPermissions::add); + + sandboxPermissions.setReadOnly(); // set permission collection to readonly + } + return sandboxPermissions; + } + + /** + * @return a read-only PermissionCollection containing the J2EE permissions + */ + public static PermissionCollection getJ2EEPermissions() { + if (j2EEPermissions == null) { + j2EEPermissions = new Permissions(); + + j2EEPermissions.add(new AWTPermission("accessClipboard")); + // disabled because we can't at this time prevent an + // application from accessing other applications' event + // queues, or even prevent access to security dialog queues. + // + // j2EEPermissions.add(new AWTPermission("accessEventQueue")); + j2EEPermissions.add(new RuntimePermission("exitVM")); + j2EEPermissions.add(new RuntimePermission("loadLibrary")); + j2EEPermissions.add(new RuntimePermission("queuePrintJob")); + j2EEPermissions.add(new SocketPermission("*", "connect")); + j2EEPermissions.add(new SocketPermission("localhost:1024-", "accept, listen")); + j2EEPermissions.add(new FilePermission("*", "read, write")); + j2EEPermissions.add(new PropertyPermission("*", PROPERTY_READ_ACTION)); + + j2EEPermissions.setReadOnly(); // set permission collection to readonly + } + return j2EEPermissions; + } + + /** + * @return a read-only PermissionCollection containing the JNLP RIA permissions + * @see Secure System Properties + */ + public static PermissionCollection getJnlpRiaPermissions() { + if (jnlpRIAPermissions == null) { + jnlpRIAPermissions = new Permissions(); + + jnlpRIAPermissions.add(new PropertyPermission(AWT_AA_FONT_SETTINGS, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(HTTP_AGENT, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(HTTP_KEEP_ALIVE, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(AWT_SYNC_LWREQUESTS, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(AWT_WINDOW_LOCATION_BY_PLATFORM, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(AWT_DISABLE_MIXING, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(WEBSTART_JAUTHENTICATOR, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(SWING_DEFAULT_LF, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(AWT_NO_ERASE_BACKGROUND, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(AWT_ERASE_BACKGROUND_ON_RESIZE, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(JAVA2D_D3D, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(JAVA2D_DPI_AWARE, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(JAVA2D_NO_DDRAW, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(JAVA2D_OPENGL, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(SWING_BOLD_METAL, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(SWING_METAL_THEME, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(SWING_NO_XP, PROPERTY_RW_ACTION)); + jnlpRIAPermissions.add(new PropertyPermission(SWING_USE_SYSTEM_FONT, PROPERTY_RW_ACTION)); + + jnlpRIAPermissions.setReadOnly(); // set permission collection to readonly + } + return jnlpRIAPermissions; + } + + /** + * @param file + * @return a read-only PermissionCollection containing the URL permissions + */ + private static PermissionCollection getUrlPermissions(final JNLPFile file) { + if (urlPermissions == null) { + urlPermissions = new Permissions(); + + if (urlPermissionClass != null && urlPermissionConstructor != null) { + final PermissionCollection permissions = new Permissions(); + for (final JARDesc jar : file.getResources().getJARs()) { + try { + // Allow applets all HTTP methods (ex POST, GET) with any request headers + // on resources anywhere recursively in or below the applet codebase, only on + // default ports and ports explicitly specified in resource locations + final URI resourceLocation = jar.getLocation().toURI().normalize(); + final URI host = getHost(resourceLocation); + final String hostUriString = host.toString(); + final String urlPermissionUrlString = appendRecursiveSubdirToCodebaseHostString(hostUriString); + final Permission p = urlPermissionConstructor.newInstance(urlPermissionUrlString); + permissions.add(p); + } catch (final ReflectiveOperationException e) { + LOG.error("Exception while attempting to reflectively generate a URLPermission, probably not running on Java 8+?", e); + } catch (final URISyntaxException e) { + LOG.error("Could not determine codebase host for resource at " + jar.getLocation() + " while generating URLPermissions", e); + } + } + try { + final URI codebase = file.getNotNullProbableCodeBase().toURI().normalize(); + final URI host = getHost(codebase); + final String codebaseHostUriString = host.toString(); + final String urlPermissionUrlString = appendRecursiveSubdirToCodebaseHostString(codebaseHostUriString); + final Permission p = urlPermissionConstructor.newInstance(urlPermissionUrlString); + permissions.add(p); + } catch (final ReflectiveOperationException e) { + LOG.error("Exception while attempting to reflectively generate a URLPermission, probably not running on Java 8+?", e); + } catch (final URISyntaxException e) { + LOG.error("Could not determine codebase host for codebase " + file.getCodeBase() + " while generating URLPermissions", e); + } + } + + urlPermissions.setReadOnly(); + } + + return urlPermissions; + } + + /** + * @param cs the CodeSource to get permissions for + * @return a read-only PermissionCollection containing the basic permissions granted depending on + * the {@link ApplicationEnvironment} + */ + public static PermissionCollection getPermissions(final JNLPFile file, final CodeSource cs, final ApplicationEnvironment applicationEnvironment) { + + if (applicationEnvironment == ApplicationEnvironment.ALL) { + final Policy customTrustedPolicy = getCustomTrustedPolicy(); + if (customTrustedPolicy != null) { + final PermissionCollection customPolicyPermissions = customTrustedPolicy.getPermissions(cs); + customPolicyPermissions.setReadOnly(); + return customPolicyPermissions; + } + + final PermissionCollection allPermissions = new Permissions(); + allPermissions.add(new AllPermission()); + allPermissions.setReadOnly(); + return allPermissions; + } + + if (applicationEnvironment == ApplicationEnvironment.J2EE) { + PermissionCollection j2eePermissions = new Permissions(); + Collections.list(getSandBoxPermissions(file).elements()).forEach(j2eePermissions::add); + Collections.list(getJ2EEPermissions().elements()).forEach(j2eePermissions::add); + j2eePermissions.setReadOnly(); + return j2eePermissions; + } + + return getSandBoxPermissions(file); + } + + /** + * Check whether {@link AWTPermission} should be added, as a property is defined in the {@link JNLPRuntime} + * {@link net.sourceforge.jnlp.config.DeploymentConfiguration}. + * + * @return true, if permission should be added + * @see + */ + private static boolean shouldAddShowWindowWithoutWarningBannerAwtPermission() { + return Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_SECURITY_ALLOW_HIDE_WINDOW_WARNING)); + } + + /** + * Returns a Policy object that represents a custom policy to use instead + * of granting {@link AllPermission} to a {@link CodeSource} + * + * @return a {@link Policy} object to delegate to. May be null, which + * indicates that no policy exists and AllPermissions should be granted + * instead. + */ + private static Policy getCustomTrustedPolicy() { + final String key = ConfigurationConstants.KEY_SECURITY_TRUSTED_POLICY; + final String policyLocation = JNLPRuntime.getConfiguration().getProperty(key); + + Policy policy = null; + + if (policyLocation != null) { + try { + final URI policyUri = new URI("file://" + policyLocation); + policy = Policy.getInstance("JavaPolicy", new URIParameter(policyUri)); + } catch (Exception e) { + LOG.error("Unable to create trusted policy for policy file at " + policyLocation, e); + } + } + + return policy; + } + + /** + * Gets the host domain part of an applet's codebase. Removes path, query, and fragment, but preserves scheme, + * user info, and host. The port used is overridden with the specified port. + * + * @param codebase the applet codebase URL + * @param port the port + * @return the host domain of the codebase + */ + static URI getHostWithSpecifiedPort(final URI codebase, final int port) throws URISyntaxException { + Objects.requireNonNull(codebase); + return new URI(codebase.getScheme(), codebase.getUserInfo(), codebase.getHost(), port, null, null, null); + } + + /** + * Gets the host domain part of an applet's codebase. Removes path, query, and fragment, but preserves scheme, + * user info, host, and port. + * + * @param codebase the applet codebase URL + * @return the host domain of the codebase + */ + static URI getHost(final URI codebase) throws URISyntaxException { + Objects.requireNonNull(codebase); + return getHostWithSpecifiedPort(codebase, codebase.getPort()); + } + + /** + * Appends a recursive access marker to a codebase host, for granting Java 8 URLPermissions which are no + * more restrictive than the existing SocketPermissions + * See http://docs.oracle.com/javase/8/docs/api/java/net/URLPermission.html + * + * @param codebaseHost the applet's codebase's host domain URL as a String. Expected to be formatted as eg + * "http://example.com:8080" or "http://example.com/" + * @return the resulting String eg "http://example.com:8080/- + */ + static String appendRecursiveSubdirToCodebaseHostString(final String codebaseHost) { + Objects.requireNonNull(codebaseHost); + String result = codebaseHost; + while (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + // See http://docs.oracle.com/javase/8/docs/api/java/net/URLPermission.html + result = result + "/-"; // allow access to any resources recursively on the host domain + return result; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/RememberingSecurityUserInteractions.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/RememberingSecurityUserInteractions.java new file mode 100644 index 000000000..642e55471 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/RememberingSecurityUserInteractions.java @@ -0,0 +1,135 @@ +package net.adoptopenjdk.icedteaweb.security; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.lockingfile.StorageIoException; +import net.adoptopenjdk.icedteaweb.security.dialog.DialogProvider; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecisions; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecisionsFileStore; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.config.ConfigurationConstants; +import net.sourceforge.jnlp.config.DeploymentConfiguration; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.security.AccessType; +import net.sourceforge.jnlp.security.CertificateUtils; +import net.sourceforge.jnlp.tools.CertInformation; + +import java.security.cert.CertPath; +import java.security.cert.X509Certificate; +import java.util.Optional; + +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult.ALWAYS; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.RUN_UNSIGNED_APPLICATION; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.of; + +/** + * Interactions with user for concerning security and permission related decisions. + */ +public class RememberingSecurityUserInteractions implements SecurityUserInteractions { + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final UserDecisions userDecisions; + + public RememberingSecurityUserInteractions() { + this(new UserDecisionsFileStore()); + } + + RememberingSecurityUserInteractions(UserDecisions userDecisions) { + this.userDecisions = userDecisions; + } + + /** + * Ask the user for permission by showing an {@link net.adoptopenjdk.icedteaweb.security.dialog.UnsignedWarningDialog} + * when an application is unsigned. This is used with 'high-security' setting. + * + * @param file + * @return + * @see Java 8 Properties + */ + public AllowDeny askUserForPermissionToRunUnsignedApplication(final JNLPFile file) { + DeploymentConfiguration conf = JNLPRuntime.getConfiguration(); + if (conf == null) { + throw new StorageIoException("JNLPRuntime configuration is null. Try to reinstall IcedTea-Web"); + } + SecurityLevel securityLevel = SecurityLevel.valueOf(conf.getProperty(ConfigurationConstants.KEY_SECURITY_LEVEL)); + + if (securityLevel == SecurityLevel.VERY_HIGH) { + return AllowDeny.DENY; + } + + final Optional remembered = this.userDecisions.getUserDecisions(RUN_UNSIGNED_APPLICATION, file, AllowDeny.class); + + return remembered.orElseGet(() -> { + final RememberableResult dialogResult = DialogProvider.showUnsignedWarningDialog(file); + userDecisions.save(dialogResult.getRemember(), file, of(RUN_UNSIGNED_APPLICATION, dialogResult.getResult())); + return dialogResult.getResult(); + }); + } + + @Override + public AllowDenySandbox askUserHowToRunApplicationWithCertIssues(final JNLPFile file, final CertPath certPath, final CertInformation certInfo) { + final boolean rootInCaCerts = certInfo.isRootInCacerts(); + + AccessType accessType; + if (rootInCaCerts && !certInfo.hasSigningIssues()) { + accessType = AccessType.VERIFIED; + } else if (certInfo.isRootInCacerts()) { + accessType = AccessType.SIGNING_ERROR; + } else { + accessType = AccessType.UNVERIFIED; + } + + final String message = getMessageFor(accessType); + final boolean alwaysTrustSelected = (accessType == AccessType.VERIFIED); + final String moreInformationText = getMoreInformationText(accessType, rootInCaCerts); + + final AccessWarningResult result = DialogProvider.showJarCertWarningDialog(file, certPath.getCertificates(), certInfo.getDetailsAsStrings(), message, alwaysTrustSelected, moreInformationText); + + if (result == ALWAYS) { + certPath.getCertificates().stream() + .findFirst() + .filter(cert -> cert instanceof X509Certificate) + .map(cert -> (X509Certificate) cert) + .ifPresent(CertificateUtils::saveCertificate); + } + + switch (result) { + case YES: + case ALWAYS: + return AllowDenySandbox.ALLOW; + case SANDBOX: + return AllowDenySandbox.SANDBOX; + default: + return AllowDenySandbox.DENY; + } + } + + private static String getMoreInformationText(final AccessType accessType, final boolean rootInCaCerts) { + String moreInformationText = rootInCaCerts ? + TRANSLATOR.translate("STrustedSource") : TRANSLATOR.translate("SUntrustedSource"); + + switch (accessType) { + case UNVERIFIED: + case SIGNING_ERROR: + return moreInformationText + " " + TRANSLATOR.translate("SWarnFullPermissionsIgnorePolicy"); + default: + return moreInformationText; + } + } + + private static String getMessageFor(final AccessType accessType) { + switch (accessType) { + case VERIFIED: + return TRANSLATOR.translate("SSigVerified"); + case UNVERIFIED: + return TRANSLATOR.translate("SSigUnverified"); + case SIGNING_ERROR: + return TRANSLATOR.translate("SSignatureError"); + default: + return ""; + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityLevel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityLevel.java new file mode 100644 index 000000000..864156a92 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityLevel.java @@ -0,0 +1,6 @@ +package net.adoptopenjdk.icedteaweb.security; + +public enum SecurityLevel { + HIGH, + VERY_HIGH +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityUserInteractions.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityUserInteractions.java new file mode 100644 index 000000000..5db11904f --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/SecurityUserInteractions.java @@ -0,0 +1,18 @@ +package net.adoptopenjdk.icedteaweb.security; + +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.tools.CertInformation; + +import java.security.cert.CertPath; + +/** + * Interactions with user for concerning security and permission related decisions. + */ +public interface SecurityUserInteractions { + + AllowDeny askUserForPermissionToRunUnsignedApplication(final JNLPFile file); + + AllowDenySandbox askUserHowToRunApplicationWithCertIssues(final JNLPFile file, final CertPath certPath, final CertInformation certInformation); +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AccessWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AccessWarningDialog.java new file mode 100644 index 000000000..87c6a4488 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AccessWarningDialog.java @@ -0,0 +1,66 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.LayoutPartsBuilder; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.RememberUserDecisionPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.JComponent; +import java.util.Arrays; +import java.util.List; + +/** + * + */ +public class AccessWarningDialog extends BasicSecurityDialog> { + private static final Logger LOG = LoggerFactory.getLogger(AccessWarningDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final JNLPFile file; + private final DialogButton> allowButton; + private final DialogButton> denyButton; + private RememberUserDecisionPanel rememberUserDecisionPanel; + + private AccessWarningDialog(final JNLPFile file, final String message) { + super(message); + this.file = file; + allowButton = ButtonFactory.createAllowButton(() -> new RememberableResult<>(AllowDeny.ALLOW, rememberUserDecisionPanel.getResult())); + denyButton = ButtonFactory.createDenyButton(() -> new RememberableResult<>(AllowDeny.DENY, rememberUserDecisionPanel.getResult())); + } + + public static AccessWarningDialog create(final JNLPFile jnlpFile, final String message) { + return new AccessWarningDialog(jnlpFile, message); + } + + @Override + public String createTitle() { + return "Security Warning"; + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + gridBuilder.addRows(LayoutPartsBuilder.getApplicationDetails(file)); + gridBuilder.addHorizontalSpacer(); + + rememberUserDecisionPanel = new RememberUserDecisionPanel(); + gridBuilder.addComponentRow(rememberUserDecisionPanel); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for Access warning dialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List>> createButtons() { + return Arrays.asList(allowButton, denyButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AuthenticationDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AuthenticationDialog.java new file mode 100644 index 000000000..3b10aa619 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/AuthenticationDialog.java @@ -0,0 +1,86 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * + */ +public class AuthenticationDialog extends BasicSecurityDialog> { + private static final Logger LOG = LoggerFactory.getLogger(AuthenticationDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final DialogButton> loginButton; + private final DialogButton> cancelButton; + private JTextField usernameTextField; + private JPasswordField passwordTextField; + + private AuthenticationDialog(final String message) { + super(message); + loginButton = ButtonFactory.createLoginButton(() -> { + final NamePassword value = new NamePassword(usernameTextField.getText(), passwordTextField.getPassword()); + return Optional.of(value); + }); + cancelButton = ButtonFactory.createCancelButton(Optional::empty); + } + + /** + * @param host hostname of the site or proxy requesting authentication + * @param port port number for the requested connection + * @param prompt the prompt string given by the requestor + * @param type type that defines that requestor is a Proxy or a Server + * @return + */ + public static AuthenticationDialog create(final String host, final int port, final String prompt, final String type) { + String message = TRANSLATOR.translate("SAuthenticationPrompt", type, host, prompt); + return new AuthenticationDialog(message); + } + + public Dimension getPreferredSize() { + return new Dimension(500, super.getPreferredSize().height); + } + + @Override + protected ImageIcon createIcon() { + return ImageGallery.LOGIN.asImageIcon(); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("CVPasswordTitle"); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + usernameTextField = new JTextField("", 20); + gridBuilder.addKeyComponentRow(TRANSLATOR.translate("Username"), usernameTextField); + passwordTextField = new JPasswordField("", 20); + gridBuilder.addKeyComponentRow(TRANSLATOR.translate("Password"), passwordTextField); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for AuthenticationDialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List>> createButtons() { + return Arrays.asList(loginButton, cancelButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/BasicSecurityDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/BasicSecurityDialog.java new file mode 100644 index 000000000..439f3c980 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/BasicSecurityDialog.java @@ -0,0 +1,141 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogWithResult; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.util.Collections; +import java.util.List; + +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + +public abstract class BasicSecurityDialog extends DialogWithResult { + private String message; + + public BasicSecurityDialog(String message) { + super(); + this.message = message; + } + + protected ImageIcon createIcon() { + return ImageGallery.QUESTION.asImageIcon(); + } + + protected abstract List> createButtons(); + + protected abstract JComponent createDetailPaneContent(); + + @Override + protected JPanel createContentPane() { + final JPanel contentPanel = new JPanel(new BorderLayout()); + contentPanel.add(createBanner(), BorderLayout.NORTH); + contentPanel.add(createDetails(), BorderLayout.CENTER); + contentPanel.add(createActionButtons(), BorderLayout.SOUTH); + return contentPanel; + } + + private JPanel createBanner() { + final JPanel bannerPanel = new JPanel(new BorderLayout(15, 0)); + bannerPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + bannerPanel.setBackground(Color.WHITE); + bannerPanel.add(createBannerImage(), BorderLayout.WEST); + bannerPanel.add(createBannerMessage(), BorderLayout.CENTER); + return bannerPanel; + } + + private JPanel createBannerImage() { + JPanel alignHelperPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + alignHelperPanel.setBackground(null); + final JLabel iconLabel = new JLabel(createIcon()); + alignHelperPanel.add(iconLabel); + return alignHelperPanel; + } + + private JLabel createBannerMessage() { + final JLabel bannerText = new JLabel(htmlWrap(message), SwingConstants.CENTER); + bannerText.setIconTextGap(10); + bannerText.setBackground(null); + bannerText.setFont(bannerText.getFont().deriveFont(16f)); + return bannerText; + } + + private JPanel createDetails() { + final JPanel detailPanel = new JPanel(); + detailPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + detailPanel.add(createDetailPaneContent()); + return detailPanel; + } + + private JPanel createActionButtons() { + final JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + buttonPanel.add(Box.createHorizontalGlue()); + + final List> buttons = createButtons(); + buttons.forEach(b -> { + final JButton button = b.createButton(this::close); + buttonPanel.add(button); + }); + return buttonPanel; + } + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + final String msg1 = "This is a long text that should be displayed in more than 1 line. " + + "This is a long text that should be displayed in more than 1 line. " + + "This is a long text that should be displayed in more than 1 line."; + final String msg2 = "This is a small text line." + + "\n\nDo you want to continue with no proxy or exit the application?"; + + final DialogButton exitButton = new DialogButton<>("BasicSecurityDialog 1 Title", () -> 0); + + new BasicSecurityDialog(msg1) { + @Override + public String createTitle() { + return "Security Warning 1"; + } + + @Override + protected List> createButtons() { + return Collections.singletonList(exitButton); + } + + @Override + protected JComponent createDetailPaneContent() { + return new JLabel("Detail pane content"); + } + }.showAndWait(); + + new BasicSecurityDialog(msg2) { + @Override + public String createTitle() { + return "Security Warning 2"; + } + + @Override + protected List> createButtons() { + return Collections.singletonList(exitButton); + } + + @Override + protected JComponent createDetailPaneContent() { + return new JLabel("Detail pane content"); + } + + }.showAndWait(); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/ButtonFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/ButtonFactory.java new file mode 100644 index 000000000..385b90abc --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/ButtonFactory.java @@ -0,0 +1,54 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; + +import java.util.function.Supplier; + +public class ButtonFactory { + private static final Translator TRANSLATOR = Translator.getInstance(); + + public static DialogButton createAllowButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButAllow"), onAction); + } + + public static DialogButton createCreateButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButCreate"), onAction); + } + + public static DialogButton createDenyButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButDeny"), onAction); + } + + public static DialogButton createCancelButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButCancel"), onAction); + } + + public static DialogButton createCloseButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButClose"), onAction); + } + + public static DialogButton createCancelButton(String toolTipText, final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButCancel"), onAction, toolTipText); + } + + public static DialogButton createRunButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButRun"), onAction, TRANSLATOR.translate("CertWarnRunTip")); + } + + public static DialogButton createLoginButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("Login"), onAction, TRANSLATOR.translate("LoginButtonTooltip")); + } + + public static DialogButton createSandboxButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButSandbox"), onAction, TRANSLATOR.translate("CertWarnSandboxTip")); + } + + public static DialogButton createYesButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButYes"), onAction, TRANSLATOR.translate("CertWarnHTTPSAcceptTip")); + } + + public static DialogButton createNoButton(final Supplier onAction) { + return new DialogButton<>(TRANSLATOR.translate("ButNo"), onAction, TRANSLATOR.translate("CertWarnHTTPSRejectTip")); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertInfoDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertInfoDialog.java new file mode 100644 index 000000000..173253375 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertInfoDialog.java @@ -0,0 +1,101 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.CertificateDetailsPanel; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogWithResult; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.security.cert.Certificate; +import java.util.Collections; +import java.util.List; + +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; + +/** + * + */ +public class CertInfoDialog extends DialogWithResult { + private static final Logger LOG = LoggerFactory.getLogger(CertInfoDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private CertificateDetailsPanel certificateDetailsPanel; + private final DialogButton closeButton; + + private List certificates; + + private CertInfoDialog(final Dialog owner, final List certificates) { + super(owner); + this.certificates = certificates; + this.closeButton = ButtonFactory.createCloseButton(() -> null); + } + + public static CertInfoDialog create(final Dialog owner, final List certificates) { + return new CertInfoDialog(owner, certificates); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("SCertificateDetails"); + } + + private List> createButtons() { + return Collections.singletonList(closeButton); + } + + private JButton createCopyToClipboardButton() { + JButton copyToClipboard = new JButton(R("ButCopy")); + copyToClipboard.addActionListener(e -> { + certificateDetailsPanel.copyToClipboard(); + }); + return copyToClipboard; + } + + @Override + protected JPanel createContentPane() { + final JPanel contentPanel = new JPanel(new BorderLayout(10, 10)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + contentPanel.add(new CertificateDetailsPanel(certificates), BorderLayout.CENTER); + contentPanel.add(createActionButtons(), BorderLayout.SOUTH); + return contentPanel; + } + + private JPanel createActionButtons() { + final JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.add(Box.createHorizontalGlue()); + + buttonPanel.add(createCopyToClipboardButton()); + + final List> buttons = createButtons(); + buttons.forEach(b -> { + final JButton button = b.createButton(this::close); + buttonPanel.add(button); + }); + + return buttonPanel; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDetailsDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDetailsDialog.java new file mode 100644 index 000000000..071ef4478 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDetailsDialog.java @@ -0,0 +1,164 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.CertificateDetailsPanel; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogWithResult; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.GridLayout; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + +/** + * + */ +public class CertWarningDetailsDialog extends DialogWithResult { + private static final Logger LOG = LoggerFactory.getLogger(CertWarningDetailsDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private CertificateDetailsPanel certificateDetailsPanel; + private final DialogButton closeButton; + + private final List details; + private List certificates; + + private CertWarningDetailsDialog(final Dialog owner, final JNLPFile file, final List certificates, final List certIssues) { + super(owner); + this.certificates = certificates; + details = new ArrayList<>(certIssues); + + // Show signed JNLP warning if the signed main jar does not have a + // signed JNLP file and the launching JNLP file contains special properties + if (file != null && file.requiresSignedJNLPWarning()) { + details.add(TRANSLATOR.translate("SJNLPFileIsNotSigned")); + } + + this.closeButton = ButtonFactory.createCloseButton(() -> null); + } + + public static CertWarningDetailsDialog create(final Dialog owner, final JNLPFile file, final List certificates, final List certIssues) { + return new CertWarningDetailsDialog(owner, file, certificates, certIssues); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("MoreInformation"); + } + + private List> createButtons() { + return Arrays.asList(closeButton); + } + + private JButton createShowCertificateDetailsButton() { + final JButton button = new JButton(TRANSLATOR.translate("SCertificateDetails")); + return button; + } + + private JButton createCopyToClipboardButton() { + JButton copyToClipboard = new JButton(R("ButCopy")); + copyToClipboard.addActionListener(e -> { + certificateDetailsPanel.copyToClipboard(); + }); + return copyToClipboard; + } + + private JComponent createDetailPaneContent() { + int numLabels = details.size(); + JPanel errorPanel = new JPanel(new GridLayout(numLabels, 1, 0, 10)); + errorPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + for (String detail : details) { + ImageIcon icon = null; + if (detail.equals(TRANSLATOR.translate("STrustedCertificate"))) { + icon = ImageGallery.INFO_SMALL.asImageIcon(); + } else { + icon = ImageGallery.WARNING_SMALL.asImageIcon(); + } + + final JLabel imageLabel = new JLabel(htmlWrap(detail), icon, SwingConstants.LEFT); + imageLabel.setIconTextGap(10); + errorPanel.add(imageLabel); + } + return errorPanel; + } + + @Override + protected JPanel createContentPane() { + final JPanel contentPanel = new JPanel(new BorderLayout(10, 10)); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + contentPanel.add(createDetailPaneContent(), BorderLayout.NORTH); + contentPanel.add(createCertificateDetailsCollapsiblePanel(), BorderLayout.CENTER); + contentPanel.add(createActionButtons(), BorderLayout.SOUTH); + return contentPanel; + } + + private JPanel createCertificateDetailsCollapsiblePanel() { + final JPanel collapsiblePanel = new JPanel(new BorderLayout()); + collapsiblePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + final JButton showCertificateDetailsButton = createShowCertificateDetailsButton(); + collapsiblePanel.add(showCertificateDetailsButton, BorderLayout.NORTH); + + certificateDetailsPanel = new CertificateDetailsPanel(certificates); + certificateDetailsPanel.setVisible(false); + + showCertificateDetailsButton.addActionListener(e -> { + certificateDetailsPanel.setVisible(!certificateDetailsPanel.isVisible()); + pack(); + }); + + collapsiblePanel.add(certificateDetailsPanel, BorderLayout.CENTER); + return collapsiblePanel; + } + + private JPanel createActionButtons() { + final JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.add(Box.createHorizontalGlue()); + + buttonPanel.add(createCopyToClipboardButton()); + + final List> buttons = createButtons(); + buttons.forEach(b -> { + final JButton button = b.createButton(this::close); + buttonPanel.add(button); + }); + + return buttonPanel; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDialog.java new file mode 100644 index 000000000..8d6cc6f8f --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CertWarningDialog.java @@ -0,0 +1,73 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.security.cert.Certificate; +import java.util.List; + +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; + +abstract class CertWarningDialog extends BasicSecurityDialog { + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final JNLPFile file; + private final List certificates; + private final List certIssues; + protected final boolean initiallyAlwaysTrustedSelected; + private final String moreInformationText; + + protected CertWarningDialog(final String message, final JNLPFile file, final List certificates, final List certIssues, boolean initiallyAlwaysTrustedSelected, final String moreInformationText) { + super(message); + this.file = file; + this.certificates = certificates; + this.certIssues = certIssues; + this.initiallyAlwaysTrustedSelected = initiallyAlwaysTrustedSelected; + this.moreInformationText = moreInformationText; + } + + @Override + public String createTitle() { + return TRANSLATOR.translate(initiallyAlwaysTrustedSelected ? "SSecurityApprovalRequired" : "SSecurityWarning"); + } + + protected JCheckBox createAlwaysTrustCheckbox() { + JCheckBox alwaysTrustCheckBox = new JCheckBox(R("SAlwaysTrustPublisher")); + alwaysTrustCheckBox.setEnabled(true); + alwaysTrustCheckBox.setSelected(initiallyAlwaysTrustedSelected); + return alwaysTrustCheckBox; + } + + protected JPanel createMoreInformationPanel() { + JPanel panel = new JPanel(new BorderLayout()); + final JTextArea moreInfoTextArea = new JTextArea(moreInformationText); + moreInfoTextArea.setBackground(getBackground()); + moreInfoTextArea.setWrapStyleWord(true); + moreInfoTextArea.setLineWrap(true); + moreInfoTextArea.setEditable(false); + + final JScrollPane scrollPane = new JScrollPane(moreInfoTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setHorizontalScrollBar(null); + + panel.add(scrollPane, BorderLayout.CENTER); + JButton moreInfoButton = new JButton(TRANSLATOR.translate("ButMoreInformation")); + + moreInfoButton.addActionListener((e) -> DialogProvider.showMoreInfoDialog(certificates, certIssues, file)); + JPanel alignHelperPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + alignHelperPanel.add(moreInfoButton); + panel.add(alignHelperPanel, BorderLayout.SOUTH); + panel.setPreferredSize(new Dimension(720, 110)); + return panel; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CreateShortcutDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CreateShortcutDialog.java new file mode 100644 index 000000000..4ef05d62c --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/CreateShortcutDialog.java @@ -0,0 +1,122 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.information.MenuDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.information.ShortcutDesc; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.RememberUserDecisionPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.LayoutPartsBuilder; +import net.adoptopenjdk.icedteaweb.security.dialog.result.CreateShortcutResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny.valueOf; + +/** + * + */ +public class CreateShortcutDialog extends BasicSecurityDialog>> { + private static final Logger LOG = LoggerFactory.getLogger(CreateShortcutDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final JNLPFile file; + private final DialogButton>> createButton; + private final DialogButton>> cancelButton; + private JCheckBox desktopCheckBox; + private JCheckBox menuCheckBox; + private RememberUserDecisionPanel rememberUserDecisionPanel; + + private CreateShortcutDialog(final JNLPFile file, final String message) { + super(message); + this.file = file; + createButton = ButtonFactory.createCreateButton(() -> { + final CreateShortcutResult shortcutResult = new CreateShortcutResult(valueOf(desktopCheckBox), valueOf(menuCheckBox)); + return Optional.of(new RememberableResult<>(shortcutResult, rememberUserDecisionPanel.getResult())); + }); + cancelButton = ButtonFactory.createCancelButton(Optional::empty); + } + + public static CreateShortcutDialog create(final JNLPFile jnlpFile) { + return new CreateShortcutDialog(jnlpFile, TRANSLATOR.translate("SDesktopShortcut")); + } + + private JCheckBox createDesktopCheckBox() { + final Boolean applicationRequested = Optional.ofNullable(file.getInformation()) + .map(InformationDesc::getShortcut) + .map(ShortcutDesc::onDesktop) + .orElse(false); + + final String textKey = applicationRequested ? "EXAWdesktopWants" : "EXAWdesktopDontWants"; + + return new JCheckBox(TRANSLATOR.translate(textKey), applicationRequested); + } + + private JCheckBox createMenuCheckBox() { + + final Boolean includeInMenuRequested = Optional.ofNullable(file.getInformation()) + .map(InformationDesc::getShortcut) + .map(ShortcutDesc::toMenu) + .orElse(false); + + final Optional subMenu = Optional.ofNullable(file.getInformation()) + .map(InformationDesc::getShortcut) + .map(ShortcutDesc::getMenu) + .map(MenuDesc::getSubMenu); + + if (includeInMenuRequested) { + final String text; + if (subMenu.isPresent()) { + text = TRANSLATOR.translate("EXAWsubmenu", subMenu.get()); + } else { + text = TRANSLATOR.translate("EXAWmenuWants"); + } + return new JCheckBox(text, true); + + } else { + return new JCheckBox(TRANSLATOR.translate("EXAWmenuDontWants"), false); + } + } + + @Override + public String createTitle() { + return "Security Warning"; + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + gridBuilder.addRows(LayoutPartsBuilder.getApplicationDetails(file)); + gridBuilder.addHorizontalSpacer(); + + desktopCheckBox = createDesktopCheckBox(); + gridBuilder.addComponentRow(desktopCheckBox); + menuCheckBox = createMenuCheckBox(); + gridBuilder.addComponentRow(menuCheckBox); + + gridBuilder.addHorizontalSpacer(); + + rememberUserDecisionPanel = new RememberUserDecisionPanel(); + gridBuilder.addComponentRow(rememberUserDecisionPanel); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for create shortcut dialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List>>> createButtons() { + return Arrays.asList(createButton, cancelButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/DialogProvider.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/DialogProvider.java new file mode 100644 index 000000000..74ac22cfd --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/DialogProvider.java @@ -0,0 +1,142 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.adoptopenjdk.icedteaweb.security.dialog.result.CreateShortcutResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; +import net.sourceforge.jnlp.JNLPFile; + +import java.awt.Dialog; +import java.net.URL; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static java.util.Optional.ofNullable; + +/** + * This is a dialog provider that is able to show various application dialogs and wait for the user input to + * deliver it as specific result object. There are no side-effects during user interaction by the dialog code. + * All user decisions are provided in the result. + */ +public class DialogProvider { + private static final Logger LOG = LoggerFactory.getLogger(DialogProvider.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + public static RememberableResult showReadWriteFileAccessWarningDialog(final JNLPFile file, final String filePath) { + return showFileAccessWarningDialog(file, "SFileReadWriteAccess", filePath); + } + + public static RememberableResult showReadFileAccessWarningDialog(final JNLPFile file, final String filePath) { + return showFileAccessWarningDialog(file, "SFileReadAccess", filePath); + } + + public static RememberableResult showWriteFileAccessWarningDialog(final JNLPFile file, final String filePath) { + return showFileAccessWarningDialog(file, "SFileWriteAccess", filePath); + } + + private static RememberableResult showFileAccessWarningDialog(final JNLPFile file, String messageKey, final String filePath) { + String message = ofNullable(filePath) + // .map(FileUtils::displayablePath) // TODO: we should discuss if this is really wanted + .map(p -> TRANSLATOR.translate(messageKey, p)) + .orElse(TRANSLATOR.translate(messageKey, "AFileOnTheMachine")); + + return showAccessWarningDialog(file, message); + } + + public static RememberableResult showReadClipboardAccessWarningDialog(final JNLPFile file) { + return showClipboardAccessWarningDialog(file, "SClipboardReadAccess"); + } + + public static RememberableResult showWriteClipboardAccessWarningDialog(final JNLPFile file) { + return showClipboardAccessWarningDialog(file, "SClipboardWriteAccess"); + } + + private static RememberableResult showClipboardAccessWarningDialog(final JNLPFile file, String messageKey) { + String message = TRANSLATOR.translate(messageKey); + return showAccessWarningDialog(file, message); + } + + public static RememberableResult showPrinterAccessWarningDialog(final JNLPFile file) { + String message = TRANSLATOR.translate("SPrinterAccess"); + return showAccessWarningDialog(file, message); + } + + public static RememberableResult showNetworkAccessWarningDialog(final JNLPFile file, final Object address) { + // TODO: Network access seems not to be used up to now, therefore it is unclear what type address really will be + String message = ofNullable(address) + .map(a -> TRANSLATOR.translate("SNetworkAccess", a)) + .orElse(TRANSLATOR.translate("SNetworkAccess", "")); + return showAccessWarningDialog(file, message); + } + + public static RememberableResult showAccessWarningDialog(final JNLPFile file, final String message) { + final AccessWarningDialog dialogWithResult = AccessWarningDialog.create(file, message); + return dialogWithResult.showAndWait(); + } + + public static Optional> showCreateShortcutDialog(final JNLPFile file) { + final CreateShortcutDialog createShortcutDialog = CreateShortcutDialog.create(file); + return createShortcutDialog.showAndWait(); + } + + public static void showCertWarningDetailsDialog(final Dialog owner, final JNLPFile file, final List certificates, final List certIssues) { + CertWarningDetailsDialog dialog = CertWarningDetailsDialog.create(owner, file, certificates, certIssues); + dialog.showAndWait(); + } + + public static RememberableResult showUnsignedWarningDialog(final JNLPFile file) { + final UnsignedWarningDialog unsignedWarningDialog = UnsignedWarningDialog.create(file); + return unsignedWarningDialog.showAndWait(); + } + + public static AccessWarningResult showHttpsCertTrustDialog(final JNLPFile file, final Certificate certificate, final boolean rootInCaCerts, final List certificates, final List certIssues) { + final HttpsCertTrustDialog dialog = HttpsCertTrustDialog.create(file, certificate, rootInCaCerts, certificates, certIssues); + return dialog.showAndWait(); + } + + public static AccessWarningResult showJarCertWarningDialog(final JNLPFile file, final List certificates, final List certIssues, final String message, final boolean alwaysTrustSelected, final String moreInformationText) { + final JarCertWarningDialog dialog = JarCertWarningDialog.create(message, file, certificates, certIssues, moreInformationText, alwaysTrustSelected); + return dialog.showAndWait(); + } + + public static RememberableResult showPartiallySignedWarningDialog(final JNLPFile file) { + final PartiallySignedWarningDialog dialog = PartiallySignedWarningDialog.create(file); + return dialog.showAndWait(); + } + + public static RememberableResult showMissingALACAttributeDialog(final JNLPFile file, final Set locations) { + final MissingALACAttributeDialog dialog = MissingALACAttributeDialog.create(file, locations); + return dialog.showAndWait(); + } + + public static RememberableResult showMatchingALACAttributeDialog(final JNLPFile file, final Set locations) { + final MatchingALACAttributeDialog dialog = MatchingALACAttributeDialog.create(file, locations); + return dialog.showAndWait(); + } + + public static RememberableResult showMissingPermissionsAttributeDialog(final JNLPFile file) { + final MissingPermissionsAttributeDialog dialog = MissingPermissionsAttributeDialog.create(file); + return dialog.showAndWait(); + } + + public static void showCertInfoDialog(final Dialog owner, final List certificates) { + CertInfoDialog dialog = CertInfoDialog.create(owner, certificates); + dialog.showAndWait(); + } + + public static NamePassword showAuthenticationDialog(final String host, final int port, final String prompt, final String type) { + AuthenticationDialog dialog = AuthenticationDialog.create(host, port, prompt, type); + return dialog.showAndWait().orElse(null); + } + + public static void showMoreInfoDialog(final List certificates, final List certIssues, final JNLPFile file) { + DialogProvider.showCertWarningDetailsDialog(null, file, certificates, certIssues); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/HttpsCertTrustDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/HttpsCertTrustDialog.java new file mode 100644 index 000000000..6b19e725e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/HttpsCertTrustDialog.java @@ -0,0 +1,99 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.security.SecurityUtil; + +import javax.security.auth.x500.X500Principal; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * + */ +public class HttpsCertTrustDialog extends CertWarningDialog { + private static final Logger LOG = LoggerFactory.getLogger(HttpsCertTrustDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final DialogButton yesButton; + private final DialogButton noButton; + private final JNLPFile file; + private final Certificate certificate; + private final boolean rootInCaCerts; + + + private HttpsCertTrustDialog(final String message, final JNLPFile file, final Certificate certificate, final boolean rootInCaCerts, final List certificates, final List certIssues, final String moreInformationText) { + super(message, file, certificates, certIssues, false, moreInformationText); + this.file = file; + this.certificate = certificate; + this.rootInCaCerts = rootInCaCerts; + + this.yesButton = ButtonFactory.createYesButton(() -> AccessWarningResult.YES); + this.noButton = ButtonFactory.createNoButton(() -> AccessWarningResult.NO); + } + + public static HttpsCertTrustDialog create(final JNLPFile jnlpFile, final Certificate certificate, final boolean rootInCaCerts, final List certificates, final List certIssues) { + final String message = TRANSLATOR.translate("SHttpsUnverified") + " " + TRANSLATOR.translate("Continue"); + final String moreInformationText = getMoreInformationText(rootInCaCerts); + return new HttpsCertTrustDialog(message, jnlpFile, certificate, rootInCaCerts, certificates, certIssues, moreInformationText); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + String name; + String publisher = ""; + if (certificate instanceof X509Certificate) { + name = SecurityUtil.getCN(Optional.ofNullable(certificate) + .map(cert -> (X509Certificate) certificate) + .map(X509Certificate::getSubjectX500Principal) + .map(X500Principal::getName) + .orElse("")); + publisher = name; + } else { + name = file.getInformation().getTitle(); + } + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Name"), name); + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Publisher"), publisher); + + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(createMoreInformationPanel()); + + gridBuilder.addHorizontalSpacer(); + + + gridBuilder.addComponentRow(createAlwaysTrustCheckbox()); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for HttpsCertWarningDialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List> createButtons() { + return Arrays.asList(yesButton, noButton); + } + + @Override + protected ImageIcon createIcon() { + return ImageGallery.WARNING.asImageIcon(); + } + + private static String getMoreInformationText(final boolean rootInCaCerts) { + return rootInCaCerts ? TRANSLATOR.translate("STrustedSource") : TRANSLATOR.translate("SUntrustedSource"); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/JarCertWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/JarCertWarningDialog.java new file mode 100644 index 000000000..3fec9e56c --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/JarCertWarningDialog.java @@ -0,0 +1,84 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.LayoutPartsBuilder; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.List; + +/** + * TODO: check if advancedOptions (temporary permissions) should still be supported + * + * + */ +public class JarCertWarningDialog extends CertWarningDialog { + private static final Logger LOG = LoggerFactory.getLogger(JarCertWarningDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final DialogButton runButton; + private final DialogButton sandboxButton; + private final DialogButton cancelButton; + + private final JNLPFile file; + private final JCheckBox alwaysTrustCheckbox; + + protected JarCertWarningDialog(final String message, final JNLPFile file, final List certificates, final List certIssues, final String moreInformationText, final boolean alwaysTrustSelected) { + super(message, file, certificates, certIssues, alwaysTrustSelected, moreInformationText); + this.file = file; + runButton = ButtonFactory.createRunButton(() -> alwaysTrustSelected ? AccessWarningResult.ALWAYS : AccessWarningResult.YES); + sandboxButton = ButtonFactory.createSandboxButton(() -> AccessWarningResult.SANDBOX); + sandboxButton.setEnabled(!alwaysTrustSelected); + cancelButton = ButtonFactory.createCancelButton(TRANSLATOR.translate("CertWarnCancelTip"), () -> AccessWarningResult.NO); + alwaysTrustCheckbox = createAlwaysTrustCheckbox(sandboxButton); + } + + public static JarCertWarningDialog create(final String message, final JNLPFile jnlpFile, final List certificates, final List certIssues, final String moreInformationText, final boolean alwaysTrustSelected) { + return new JarCertWarningDialog(message, jnlpFile, certificates, certIssues, moreInformationText, alwaysTrustSelected); + } + + @Override + protected ImageIcon createIcon() { + return initiallyAlwaysTrustedSelected ? ImageGallery.QUESTION.asImageIcon() : ImageGallery.WARNING.asImageIcon(); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + gridBuilder.addRows(LayoutPartsBuilder.getApplicationDetails(file)); + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(createMoreInformationPanel()); + + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(alwaysTrustCheckbox); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for CertWarningDialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List> createButtons() { + return Arrays.asList(runButton, sandboxButton, cancelButton); + } + + protected JCheckBox createAlwaysTrustCheckbox(final DialogButton sandboxButton) { + JCheckBox alwaysTrustCheckBox = super.createAlwaysTrustCheckbox(); + alwaysTrustCheckBox.addActionListener(e -> sandboxButton.setEnabled(!alwaysTrustCheckBox.isSelected())); + return alwaysTrustCheckBox; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MatchingALACAttributeDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MatchingALACAttributeDialog.java new file mode 100644 index 000000000..a6564d439 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MatchingALACAttributeDialog.java @@ -0,0 +1,66 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.ReferencesPanel; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.ImageIcon; +import java.net.URL; +import java.util.Set; + +/** + * The ALAC attribute identifies the locations where your signed application is expected to + * be found. + * + * + */ +public class MatchingALACAttributeDialog extends MissingAttributeDialog { + private static final Logger LOG = LoggerFactory.getLogger(MatchingALACAttributeDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private static Set locations; + + public MatchingALACAttributeDialog(final String message, final JNLPFile file) { + super(message, file); + } + + public static MatchingALACAttributeDialog create(final JNLPFile file, final Set locations) { + MatchingALACAttributeDialog.locations = locations; + String message = createMessage(file.getTitle(), file.getNotNullProbableCodeBase()); + return new MatchingALACAttributeDialog(message, file); + } + + @Override + protected ImageIcon createIcon() { + return ImageGallery.QUESTION.asImageIcon(); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("MatchingALACAttribute"); + } + + @Override + protected void getAdditionalApplicationDetails(final GridBagPanelBuilder gridBuilder) { + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Codebase"), file.getNotNullProbableCodeBase().toString()); + gridBuilder.addHorizontalSpacer(); + gridBuilder.addComponentRow(createLocationList(locations)); + } + + private static ReferencesPanel createLocationList(final Set locations) { + return new ReferencesPanel(TRANSLATOR.translate("MatchingALACAttributeLocationListTitle"), locations); + } + + @Override + protected ReferencesPanel createMoreInformationPanel() { + return new ReferencesPanel(TRANSLATOR.translate("MatchingALACAttributeMoreInfo")); + } + + private static String createMessage(final String applicationName, final URL codebase) { + return TRANSLATOR.translate("MatchingALACAttributeMessage", applicationName, codebase); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingALACAttributeDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingALACAttributeDialog.java new file mode 100644 index 000000000..8294e8d2e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingALACAttributeDialog.java @@ -0,0 +1,64 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.ReferencesPanel; +import net.sourceforge.jnlp.JNLPFile; + +import java.net.URL; +import java.util.Set; + +/** + * Missing Application-Library-Allowable-Codebase (ALAC) Attribute Dialog. This dialog is shown if + * the ALAC attribute is not provided in the manifest. The dialog lists the multiple hosts that + * correspond to the locations of the JAR file and the JNLP file. The user can decide to run the + * application and remember the decision to show this dialog again for the application or domain. + * + * The ALAC attribute identifies the locations where your signed application is expected to be + * found. + * + * + */ +public class MissingALACAttributeDialog extends MissingAttributeDialog { + private static final Logger LOG = LoggerFactory.getLogger(MissingALACAttributeDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private static Set locations; + + public MissingALACAttributeDialog(final String message, final JNLPFile file) { + super(message, file); + } + + public static MissingALACAttributeDialog create(final JNLPFile file, final Set locations) { + MissingALACAttributeDialog.locations = locations; + String message = createMessage(file.getTitle(), file.getNotNullProbableCodeBase()); + return new MissingALACAttributeDialog(message, file); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("MissingALACAttribute"); + } + + @Override + protected void getAdditionalApplicationDetails(final GridBagPanelBuilder gridBuilder) { + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Codebase"), file.getNotNullProbableCodeBase().toString()); + gridBuilder.addHorizontalSpacer(); + gridBuilder.addComponentRow(createLocationList(locations)); + } + + private static ReferencesPanel createLocationList(final Set locations) { + return new ReferencesPanel(TRANSLATOR.translate("MissingALACAttributeLocationListTitle"), locations); + } + + @Override + protected ReferencesPanel createMoreInformationPanel() { + return new ReferencesPanel(TRANSLATOR.translate("MissingALACAttributeMoreInfo")); + } + + private static String createMessage(final String applicationName, final URL codebase) { + return TRANSLATOR.translate("MissingALACAttributeMessage", applicationName, codebase); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingAttributeDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingAttributeDialog.java new file mode 100644 index 000000000..eb61fac50 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingAttributeDialog.java @@ -0,0 +1,73 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.image.ImageGallery; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.ReferencesPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.RememberUserDecisionPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.LayoutPartsBuilder; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import java.util.Arrays; +import java.util.List; + +public abstract class MissingAttributeDialog extends BasicSecurityDialog> { + private static final Logger LOG = LoggerFactory.getLogger(MissingAttributeDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final DialogButton> cancelButton; + private final DialogButton> runButton; + protected RememberUserDecisionPanel rememberUserDecisionPanel; + + protected JNLPFile file; + + protected MissingAttributeDialog(final String message, final JNLPFile file) { + super(message); + this.file = file; + runButton = ButtonFactory.createRunButton(() -> new RememberableResult<>(AllowDeny.ALLOW, rememberUserDecisionPanel.getResult())); + cancelButton = ButtonFactory.createCancelButton(() -> new RememberableResult<>(AllowDeny.DENY, rememberUserDecisionPanel.getResult())); + } + + @Override + protected ImageIcon createIcon() { + return ImageGallery.WARNING.asImageIcon(); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + gridBuilder.addRows(LayoutPartsBuilder.getApplicationDetails(file)); + getAdditionalApplicationDetails(gridBuilder); + + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(createMoreInformationPanel()); + + gridBuilder.addHorizontalSpacer(); + + rememberUserDecisionPanel = new RememberUserDecisionPanel(); + gridBuilder.addComponentRow(rememberUserDecisionPanel); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for MissingAttributeDialog!", e); + } + return gridBuilder.createGrid(); + } + + protected abstract void getAdditionalApplicationDetails(final GridBagPanelBuilder gridBuilder); + + protected abstract ReferencesPanel createMoreInformationPanel(); + + @Override + protected List>> createButtons() { + return Arrays.asList(runButton, cancelButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingPermissionsAttributeDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingPermissionsAttributeDialog.java new file mode 100644 index 000000000..30821a44f --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/MissingPermissionsAttributeDialog.java @@ -0,0 +1,52 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.ReferencesPanel; +import net.sourceforge.jnlp.JNLPFile; + +import java.net.URL; + +/** + * This security dialog is shown if the permissions attribute is not provided in the JNLP. The user can decide + * to run the application and remember the decision to show this dialog again for the application or domain. + * + * The Permissions attribute is used to verify that the permissions level requested by the application when + * it runs matches the permissions level that was set when the JAR file was created. + * + * + */ +public class MissingPermissionsAttributeDialog extends MissingAttributeDialog { + private static final Logger LOG = LoggerFactory.getLogger(MissingPermissionsAttributeDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private MissingPermissionsAttributeDialog(final String message, final JNLPFile file) { + super(message, file); + } + + @Override + protected void getAdditionalApplicationDetails(final GridBagPanelBuilder gridBuilder) { + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Codebase"), file.getNotNullProbableCodeBase().toString()); + } + + public static MissingPermissionsAttributeDialog create(final JNLPFile file) { + String message = createMessage(file.getTitle(), file.getNotNullProbableCodeBase()); + return new MissingPermissionsAttributeDialog(message, file); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("MissingPermissionsAttribute"); + } + + @Override + protected ReferencesPanel createMoreInformationPanel() { + return new ReferencesPanel(TRANSLATOR.translate("MissingPermissionsAttributeMoreInfo")); + } + + private static String createMessage(final String applicationName, final URL codebase) { + return TRANSLATOR.translate("MissingPermissionsAttributeMessage", applicationName, codebase); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactory.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactory.java new file mode 100644 index 000000000..eab1a7438 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactory.java @@ -0,0 +1,331 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.DialogFactory; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.resources.Resource; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AccessWarningResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.adoptopenjdk.icedteaweb.security.dialog.result.CreateShortcutResult; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecision; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecisions; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecisionsFileStore; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.SecurityDelegate; +import net.sourceforge.jnlp.security.AccessType; +import net.sourceforge.jnlp.security.CertVerifier; +import net.sourceforge.jnlp.security.HttpsCertVerifier; + +import java.awt.Component; +import java.awt.Window; +import java.net.URL; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny.ALLOW; +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny.DENY; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.CREATE_DESKTOP_SHORTCUT; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.CREATE_MENU_SHORTCUT; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.RUN_MATCHING_ALAC_APPLICATION; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.RUN_MISSING_ALAC_APPLICATION; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.RUN_MISSING_PERMISSIONS_APPLICATION; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key.RUN_PARTIALLY_APPLICATION; +import static net.adoptopenjdk.icedteaweb.userdecision.UserDecision.of; +import static net.sourceforge.jnlp.security.AccessType.PARTIALLY_SIGNED; +import static net.sourceforge.jnlp.security.AccessType.SIGNING_ERROR; +import static net.sourceforge.jnlp.security.AccessType.UNSIGNED; +import static net.sourceforge.jnlp.security.AccessType.UNVERIFIED; +import static net.sourceforge.jnlp.security.AccessType.VERIFIED; + +public class NewDialogFactory implements DialogFactory { + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final UserDecisions userDecisions; + + NewDialogFactory() { + this(new UserDecisionsFileStore()); + } + + NewDialogFactory(final UserDecisions userDecisions) { + this.userDecisions = userDecisions; + } + + @Override + public AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, final JNLPFile file, final Object[] extras) { + if (Arrays.asList(VERIFIED, UNVERIFIED, PARTIALLY_SIGNED, UNSIGNED, SIGNING_ERROR).contains(accessType)) { + throw new RuntimeException(accessType + " cannot be displayed in AccessWarningDialog"); + } + + if (accessType == AccessType.CREATE_DESKTOP_SHORTCUT) { + return askForPermissionToCreateShortcuts(file); + } else { + return askForAccessPermission(accessType, file, extras); + } + } + + @Override + public YesNoSandbox showCertWarningDialog(final AccessType accessType, final JNLPFile file, final CertVerifier certVerifier, final SecurityDelegate securityDelegate) { + final AccessWarningResult certWarningResult; + + final Optional certVerifierOptional = Optional.ofNullable(certVerifier); + + final List certificates = certVerifierOptional + .map(cp -> certVerifier.getCertPath()) + .map(CertPath::getCertificates) + .orElse(Collections.emptyList()); + + final List certIssues = certVerifierOptional + .map(cv -> cv.getDetails(null)) + .orElse(Collections.emptyList()); + + final boolean rootInCaCerts = certVerifierOptional.map(CertVerifier::getRootInCaCerts).orElse(Boolean.FALSE); + + if (certVerifier instanceof HttpsCertVerifier) { + certWarningResult = DialogProvider.showHttpsCertTrustDialog(file, certVerifier.getPublisher(null), rootInCaCerts, certificates, certIssues); + } else { + final String message = getMessageFor(accessType); + final String moreInformationText = getMoreInformationText(accessType, rootInCaCerts); + final boolean alwaysTrustSelected = (accessType == AccessType.VERIFIED); + + certWarningResult = DialogProvider.showJarCertWarningDialog(file, certificates, certIssues, message, alwaysTrustSelected, moreInformationText); + } + switch (certWarningResult) { + case YES: + return YesNoSandbox.yes(); + case SANDBOX: + return YesNoSandbox.sandbox(); + default: + return YesNoSandbox.no(); + } + } + + private static String getMoreInformationText(final AccessType accessType, final boolean rootInCaCerts) { + String moreInformationText = rootInCaCerts ? + TRANSLATOR.translate("STrustedSource") : TRANSLATOR.translate("SUntrustedSource"); + + switch (accessType) { + case UNVERIFIED: + case SIGNING_ERROR: + return moreInformationText + " " + TRANSLATOR.translate("SWarnFullPermissionsIgnorePolicy"); + default: + return moreInformationText; + } + } + + private static String getMessageFor(final AccessType accessType) { + switch (accessType) { + case VERIFIED: + return TRANSLATOR.translate("SSigVerified"); + case UNVERIFIED: + return TRANSLATOR.translate("SSigUnverified"); + case SIGNING_ERROR: + return TRANSLATOR.translate("SSignatureError"); + default: + return ""; + } + } + + @Override + public YesNoSandbox showPartiallySignedWarningDialog(final JNLPFile file, final CertVerifier certVerifier, final SecurityDelegate securityDelegate) { + final Optional remembered = this.userDecisions.getUserDecisions(RUN_PARTIALLY_APPLICATION, file, AllowDenySandbox.class); + + final AllowDenySandbox result = remembered.orElseGet(() -> { + final RememberableResult dialogResult = DialogProvider.showPartiallySignedWarningDialog(file); + userDecisions.save(dialogResult.getRemember(), file, of(RUN_PARTIALLY_APPLICATION, dialogResult.getResult())); + return dialogResult.getResult(); + }); + + return result == AllowDenySandbox.ALLOW ? YesNoSandbox.yes() : + result == AllowDenySandbox.SANDBOX ? YesNoSandbox.sandbox() : YesNoSandbox.no(); + } + + @Override + public NamePassword showAuthenticationPrompt(final String host, final int port, final String prompt, final String type) { + return DialogProvider.showAuthenticationDialog(host, port, prompt, type); + } + + @Override + public boolean showMissingALACAttributePanel(final JNLPFile file, final URL codeBase, final Set locations) { + // On the only place where "codeBase" is handed in, it is done as file.getCodebase(). + // In the dialog this is checked by file.getNotNullProbableCodeBase() first, which makes + // the handover of codebase obsolete here + final Optional remembered = this.userDecisions.getUserDecisions(RUN_MISSING_ALAC_APPLICATION, file, AllowDeny.class); + + final AllowDeny result = remembered.orElseGet(() -> { + final RememberableResult dialogResult = DialogProvider.showMissingALACAttributeDialog(file, locations); + userDecisions.save(dialogResult.getRemember(), file, of(RUN_MISSING_ALAC_APPLICATION, dialogResult.getResult())); + return dialogResult.getResult(); + }); + + return result == ALLOW; + } + + @Override + public boolean showMatchingALACAttributePanel(final JNLPFile file, final URL documentBase, final Set locations) { + // On the only place where "documentBase" is handed in, it is done as file.getCodebase(). + // In the dialog this is checked by file.getNotNullProbableCodeBase() first, which makes + // the handover of codebase obsolete here + final Optional remembered = this.userDecisions.getUserDecisions(RUN_MATCHING_ALAC_APPLICATION, file, AllowDeny.class); + + final AllowDeny result = remembered.orElseGet(() -> { + final RememberableResult dialogResult = DialogProvider.showMatchingALACAttributeDialog(file, locations); + userDecisions.save(dialogResult.getRemember(), file, of(RUN_MATCHING_ALAC_APPLICATION, dialogResult.getResult())); + return dialogResult.getResult(); + }); + + return result == ALLOW; + } + + @Override + public boolean showMissingPermissionsAttributeDialogue(final JNLPFile file) { + final Optional remembered = this.userDecisions.getUserDecisions(RUN_MISSING_PERMISSIONS_APPLICATION, file, AllowDeny.class); + + final AllowDeny result = remembered.orElseGet(() -> { + final RememberableResult dialogResult = DialogProvider.showMissingPermissionsAttributeDialog(file); + userDecisions.save(dialogResult.getRemember(), file, of(RUN_MISSING_PERMISSIONS_APPLICATION, dialogResult.getResult())); + return dialogResult.getResult(); + }); + + return result == ALLOW; + } + + @Override + public boolean show511Dialogue(final Resource r) { + return false; + } + + @Override + public void showMoreInfoDialog(final CertVerifier certVerifier, final JNLPFile file) { + final Optional certVerifierOptional = Optional.ofNullable(certVerifier); + + final List certificates = certVerifierOptional + .map(cp -> certVerifier.getCertPath()) + .map(CertPath::getCertificates) + .orElse(Collections.emptyList()); + + final List certIssues = certVerifierOptional + .map(cv -> cv.getDetails(null)) + .orElse(Collections.emptyList()); + + DialogProvider.showCertWarningDetailsDialog(null, file, certificates, certIssues); + } + + @Override + public void showCertInfoDialog(final CertVerifier certVerifier, final Component parent) { + final List certificates = Optional.ofNullable(certVerifier) + .map(cp -> certVerifier.getCertPath()) + .map(CertPath::getCertificates) + .orElse(Collections.emptyList()); + DialogProvider.showCertInfoDialog(null, certificates); + } + + @Override + public void showSingleCertInfoDialog(final X509Certificate c, final Window parent) { + DialogProvider.showCertInfoDialog(null, Collections.singletonList(c)); + } + + private AccessWarningPaneComplexReturn askForPermissionToCreateShortcuts(JNLPFile file) { + final Optional> rememberedDecision = getRememberedUserDecision(file).map(Optional::of); + + final Optional result = rememberedDecision.orElseGet(() -> showCreateShortcutDialog(file)); + + if (!result.isPresent()) { + return new AccessWarningPaneComplexReturn(Primitive.CANCEL); + } else { + final AccessWarningPaneComplexReturn ar; + ar = new AccessWarningPaneComplexReturn(Primitive.YES); + ar.setDesktop(new ShortcutResult(result.get().getCreateDesktopShortcut() == AllowDeny.ALLOW)); + ar.setMenu(new ShortcutResult(result.get().getCreateMenuShortcut() == AllowDeny.ALLOW)); + return ar; + } + } + + private Optional showCreateShortcutDialog(JNLPFile file) { + final Optional> result = DialogProvider.showCreateShortcutDialog(file); + return result.map(r -> { + rememberUserDecision(file, r); + return r.getResult(); + }); + } + + private Optional getRememberedUserDecision(JNLPFile file) { + final Optional createDesktop = userDecisions.getUserDecisions(CREATE_DESKTOP_SHORTCUT, file, AllowDeny.class); + final Optional createMenu = userDecisions.getUserDecisions(CREATE_MENU_SHORTCUT, file, AllowDeny.class); + + if (createDesktop.isPresent() || createMenu.isPresent()) { + return Optional.of(new CreateShortcutResult(createDesktop.orElse(DENY), createMenu.orElse(DENY))); + } + return Optional.empty(); + } + + private void rememberUserDecision(final JNLPFile file, final RememberableResult result) { + userDecisions.save(result.getRemember(), file, of(CREATE_DESKTOP_SHORTCUT, result.getResult().getCreateDesktopShortcut())); + userDecisions.save(result.getRemember(), file, of(CREATE_MENU_SHORTCUT, result.getResult().getCreateMenuShortcut())); + } + + private AccessWarningPaneComplexReturn askForAccessPermission(AccessType accessType, JNLPFile file, Object[] extras) { + final Optional rememberedDecision = getRememberedUserDecision(accessType, file); + + final AllowDeny result = rememberedDecision.orElseGet(() -> showAccessPermissionDialog(accessType, file, extras)); + + return new AccessWarningPaneComplexReturn(result == AllowDeny.ALLOW); + } + + private AllowDeny showAccessPermissionDialog(AccessType accessType, JNLPFile file, Object[] extras) { + final RememberableResult dialogResult; + + final Object extra = Optional.ofNullable(extras) + .filter(nonNullExtras -> nonNullExtras.length > 0) + .map(nonEmptyExtras -> nonEmptyExtras[0]) + .orElse(""); + + switch (accessType) { + case READ_WRITE_FILE: + dialogResult = DialogProvider.showReadWriteFileAccessWarningDialog(file, extra.toString()); + break; + case READ_FILE: + dialogResult = DialogProvider.showReadFileAccessWarningDialog(file, extra.toString()); + break; + case WRITE_FILE: + dialogResult = DialogProvider.showWriteFileAccessWarningDialog(file, extra.toString()); + break; + case CLIPBOARD_READ: + dialogResult = DialogProvider.showReadClipboardAccessWarningDialog(file); + break; + case CLIPBOARD_WRITE: + dialogResult = DialogProvider.showWriteClipboardAccessWarningDialog(file); + break; + case PRINTER: + dialogResult = DialogProvider.showPrinterAccessWarningDialog(file); + break; + case NETWORK: + dialogResult = DialogProvider.showNetworkAccessWarningDialog(file, extra); + break; + default: + dialogResult = DialogProvider.showAccessWarningDialog(file, ""); + } + + rememberUserDecision(file, accessType, dialogResult); + return dialogResult.getResult(); + } + + private Optional getRememberedUserDecision(AccessType accessType, JNLPFile file) { + return userDecisions.getUserDecisions(UserDecision.Key.valueOf(accessType), file, AllowDeny.class); + } + + private void rememberUserDecision(final JNLPFile file, final AccessType accessType, final RememberableResult result) { + userDecisions.save(result.getRemember(), file, of(accessType, result.getResult())); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/PartiallySignedWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/PartiallySignedWarningDialog.java new file mode 100644 index 000000000..64cef45ef --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/PartiallySignedWarningDialog.java @@ -0,0 +1,94 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.LayoutPartsBuilder; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.RememberUserDecisionPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.Arrays; +import java.util.List; + +/** + * + */ +public class PartiallySignedWarningDialog extends BasicSecurityDialog> { + private static final Logger LOG = LoggerFactory.getLogger(PartiallySignedWarningDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final JNLPFile file; + private final DialogButton> allowButton; + private final DialogButton> sandboxButton; + private final DialogButton> denyButton; + private RememberUserDecisionPanel rememberUserDecisionPanel; + + PartiallySignedWarningDialog(final JNLPFile file) { + super(TRANSLATOR.translate("SUnsignedSummary")); + + this.file = file; + allowButton = ButtonFactory.createAllowButton(() -> new RememberableResult<>(AllowDenySandbox.ALLOW, rememberUserDecisionPanel.getResult())); + sandboxButton = ButtonFactory.createSandboxButton(() -> new RememberableResult<>(AllowDenySandbox.SANDBOX, rememberUserDecisionPanel.getResult())); + denyButton = ButtonFactory.createDenyButton(() -> new RememberableResult<>(AllowDenySandbox.DENY, rememberUserDecisionPanel.getResult())); + } + + public static PartiallySignedWarningDialog create(final JNLPFile file) { + return new PartiallySignedWarningDialog(file); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("SPartiallySignedApplication"); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + gridBuilder.addRows(LayoutPartsBuilder.getApplicationDetails(file)); + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(createMoreInfoPanel()); + + rememberUserDecisionPanel = new RememberUserDecisionPanel(); + gridBuilder.addComponentRow(rememberUserDecisionPanel); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for partially signed warning dialog!", e); + } + return gridBuilder.createGrid(); + } + + private JPanel createMoreInfoPanel() { + JPanel panel = new JPanel(new BorderLayout()); + final JTextArea moreInfoTextArea = new JTextArea(TRANSLATOR.translate("SPartiallySignedDetail")); + moreInfoTextArea.setBackground(getBackground()); + moreInfoTextArea.setWrapStyleWord(true); + moreInfoTextArea.setLineWrap(true); + moreInfoTextArea.setEditable(false); + + final JScrollPane scrollPane = new JScrollPane(moreInfoTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setHorizontalScrollBar(null); + panel.add(scrollPane, BorderLayout.CENTER); + panel.setPreferredSize(new Dimension(720, 70)); + + return panel; + } + + @Override + protected List>> createButtons() { + return Arrays.asList(allowButton, sandboxButton, denyButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/UnsignedWarningDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/UnsignedWarningDialog.java new file mode 100644 index 000000000..26787e441 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/UnsignedWarningDialog.java @@ -0,0 +1,93 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagPanelBuilder; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.security.dialog.panel.RememberUserDecisionPanel; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.security.dialog.result.RememberableResult; +import net.adoptopenjdk.icedteaweb.ui.dialogs.DialogButton; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import java.util.Arrays; +import java.util.List; + +import static java.util.Optional.ofNullable; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; + +/** + * + */ +public class UnsignedWarningDialog extends BasicSecurityDialog> { + private static final Logger LOG = LoggerFactory.getLogger(UnsignedWarningDialog.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private final JNLPFile file; + private final DialogButton> allowButton; + private final DialogButton> denyButton; + private RememberUserDecisionPanel rememberUserDecisionPanel; + + private UnsignedWarningDialog(final JNLPFile file) { + super(TRANSLATOR.translate("SUnsignedSummary")); + this.file = file; + allowButton = ButtonFactory.createAllowButton(() -> new RememberableResult<>(AllowDeny.ALLOW, rememberUserDecisionPanel.getResult())); + denyButton = ButtonFactory.createDenyButton(() -> new RememberableResult<>(AllowDeny.DENY, rememberUserDecisionPanel.getResult())); + } + + public static UnsignedWarningDialog create(final JNLPFile file) { + return new UnsignedWarningDialog(file); + } + + @Override + protected String createTitle() { + return TRANSLATOR.translate("SUnsignedApplication"); + } + + @Override + protected JComponent createDetailPaneContent() { + final GridBagPanelBuilder gridBuilder = new GridBagPanelBuilder(); + try { + final String name = ofNullable(file) + .map(JNLPFile::getInformation) + .map(InformationDesc::getTitle) + .map(s -> s + " " + TRANSLATOR.translate("SUnverified")) + .orElse(""); + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Name"), name); + + final String codebase = ofNullable(file) + .map(JNLPFile::getCodeBase) + .map(url -> url.toString()) + .orElse(""); + gridBuilder.addKeyValueRow(TRANSLATOR.translate("Codebase"), codebase); + + final String sourceLocation = ofNullable(file) + .map(JNLPFile::getSourceLocation) + .map(url -> url.toString()) + .orElse(""); + + gridBuilder.addKeyValueRow(TRANSLATOR.translate("SourceLocation"), sourceLocation); + + gridBuilder.addHorizontalSpacer(); + + gridBuilder.addComponentRow(new JLabel(htmlWrap(TRANSLATOR.translate("SUntrustedRecommendation")))); + + gridBuilder.addHorizontalSpacer(); + + rememberUserDecisionPanel = new RememberUserDecisionPanel(); + gridBuilder.addComponentRow(rememberUserDecisionPanel); + + } catch (final Exception e) { + LOG.error("Error while trying to read properties for unsigned warning dialog!", e); + } + return gridBuilder.createGrid(); + } + + @Override + protected List>> createButtons() { + return Arrays.asList(allowButton, denyButton); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AccessWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AccessWarningDialog.png new file mode 100644 index 000000000..4b2c5e9b9 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AccessWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AuthenticationDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AuthenticationDialog.png new file mode 100644 index 000000000..5862ad4cd Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/AuthenticationDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertInfoDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertInfoDialog.png new file mode 100644 index 000000000..d38067010 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertInfoDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertWarningDetailsDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertWarningDetailsDialog.png new file mode 100644 index 000000000..13ab793c3 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CertWarningDetailsDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CreateShortcutDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CreateShortcutDialog.png new file mode 100644 index 000000000..2c02aed0b Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/CreateShortcutDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/HttpsCertTrustDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/HttpsCertTrustDialog.png new file mode 100644 index 000000000..dec05a119 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/HttpsCertTrustDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/JarCertWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/JarCertWarningDialog.png new file mode 100644 index 000000000..c82f02d54 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/JarCertWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MatchingALACAttributeDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MatchingALACAttributeDialog.png new file mode 100644 index 000000000..b46777dd1 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MatchingALACAttributeDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingALACAttributeDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingALACAttributeDialog.png new file mode 100644 index 000000000..cd8be3273 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingALACAttributeDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingPermissionsAttributeDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingPermissionsAttributeDialog.png new file mode 100644 index 000000000..bfc0f2827 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/MissingPermissionsAttributeDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/PartiallySignedWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/PartiallySignedWarningDialog.png new file mode 100644 index 000000000..43f0ef20d Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/PartiallySignedWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/UnsignedWarningDialog.png b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/UnsignedWarningDialog.png new file mode 100644 index 000000000..b4107ef31 Binary files /dev/null and b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/doc-files/UnsignedWarningDialog.png differ diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/CertificateDetailsPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/CertificateDetailsPanel.java new file mode 100644 index 000000000..743b987eb --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/CertificateDetailsPanel.java @@ -0,0 +1,292 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.security.dialog.panel; + +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.security.SecurityUtil; +import sun.security.x509.CertificateValidity; + +import javax.security.auth.x500.X500Principal; +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeSelectionModel; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * This panel displays data from X509Certificate(s) used in jar signing. + */ +public class CertificateDetailsPanel extends JPanel { + private final static Logger LOG = LoggerFactory.getLogger(CertificateDetailsPanel.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + private JTree tree; + + private static String[] columnNames = {TRANSLATOR.translate("Field"), TRANSLATOR.translate("Value")}; + + private List certs; + private List certsData; + + + public CertificateDetailsPanel(final List certificates) { + certs = certificates != null ? certificates : Collections.emptyList(); + certsData = new ArrayList<>(); + + for (Certificate cert : certs) { + certsData.add(parseCert((X509Certificate) cert)); + } + + createContent(); + } + + private void createContent() { + this.setLayout(new BorderLayout()); + + final JTextArea value = createValueTextArea(); + final JTable table = createCertDetailsTable(value); + tree = createCertPathTree(table); + + final JScrollPane treePane = new JScrollPane(tree); + final JScrollPane tablePane = new JScrollPane(table); + final JScrollPane valuePane = new JScrollPane(value); + + tree.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + table.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + value.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + treePane.setBorder(BorderFactory.createLineBorder(Color.WHITE)); + tablePane.setBorder(BorderFactory.createLineBorder(Color.WHITE)); + valuePane.setBorder(BorderFactory.createLineBorder(Color.WHITE)); + + + JSplitPane tableToValueSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tablePane, valuePane); + tableToValueSplitPane.setDividerLocation(0.70); + tableToValueSplitPane.setResizeWeight(0.70); + + JSplitPane treeToDetailsSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, treePane, tableToValueSplitPane); + treeToDetailsSplitPane.setPreferredSize(new Dimension(800, 300)); + treeToDetailsSplitPane.setDividerLocation(0.30); + treeToDetailsSplitPane.setResizeWeight(0.30); + + tableToValueSplitPane.setBorder(BorderFactory.createLineBorder(this.getBackground())); + treeToDetailsSplitPane.setBorder(BorderFactory.createLineBorder(this.getBackground())); + + add(treeToDetailsSplitPane, BorderLayout.CENTER); + } + + public void copyToClipboard() { + copyToClipboard(tree); + } + + private JTextArea createValueTextArea() { + final JTextArea valueTextArea = new JTextArea(); + valueTextArea.setEditable(false); + + return valueTextArea; + } + + private JTree createCertPathTree(final JTable table) { + JTree tree = new JTree(); + tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + + if (!certs.isEmpty()) { + X509Certificate firstCert = ((X509Certificate) certs.get(0)); + String subjectString = SecurityUtil.getCN(Optional.ofNullable(firstCert).map(c -> c.getSubjectX500Principal()).map(X500Principal::getName).orElse("")); + String issuerString = SecurityUtil.getCN(Optional.ofNullable(firstCert).map(c -> c.getIssuerX500Principal()).map(X500Principal::getName).orElse("")); + + DefaultMutableTreeNode top = new DefaultMutableTreeNode(subjectString + " (" + issuerString + ")"); + + //not self signed + if (!Objects.equals(firstCert.getSubjectDN(), firstCert.getIssuerDN()) && (certs.size() > 1)) { + X509Certificate secondCert = ((X509Certificate) certs.get(1)); + subjectString = SecurityUtil.getCN(Optional.ofNullable(secondCert).map(c -> c.getSubjectX500Principal()).map(X500Principal::getName).orElse("")); + issuerString = SecurityUtil.getCN(Optional.ofNullable(secondCert).map(c -> c.getIssuerX500Principal()).map(X500Principal::getName).orElse("")); + top.add(new DefaultMutableTreeNode(subjectString + " (" + issuerString + ")")); + } + tree.setModel(new DefaultTreeModel(top, false)); + + tree.addTreeSelectionListener(e -> { + final DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + + if (certsData.isEmpty() || node == null) { + return; + } + final int certIndex = node.isLeaf() ? 1 : 0; + + if (certsData.size() > certIndex) { + table.setModel(new DefaultTableModel(certsData.get(certIndex), columnNames)); + } + }); + } + return tree; + } + + private void copyToClipboard(final JTree tree) { + final DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + + if (certsData.isEmpty() || node == null) { + return; + } + + final int certIndex = node.isLeaf() ? 1 : 0; + + if (certsData.size() > certIndex) { + final String[][] cert = certsData.get(certIndex); + final int rows = cert.length; + final int cols = cert[0].length; + + String certString = ""; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + certString += cert[i][j]; + certString += " "; + } + certString += "\n"; + } + + final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(certString.toString()), null); + } + } + + private JTable createCertDetailsTable(final JTextArea valueTextArea) { + final Object[][] data = certsData.isEmpty() ? new Object[0][0] : certsData.get(0); + DefaultTableModel tableModel = new DefaultTableModel(data, columnNames); + + JTable table = new JTable(tableModel); + table.getTableHeader().setReorderingAllowed(false); + + final ListSelectionModel tableSelectionModel = table.getSelectionModel(); + tableSelectionModel.addListSelectionListener(e -> { + ListSelectionModel lsm = (ListSelectionModel) e.getSource(); + int minIndex = lsm.getMinSelectionIndex(); + int maxIndex = lsm.getMaxSelectionIndex(); + + for (int i = minIndex; i <= maxIndex; i++) { + if (lsm.isSelectedIndex(i)) { + valueTextArea.setText((String) table.getValueAt(i, 1)); + } + } + }); + table.setFillsViewportHeight(true); + + return table; + } + + + private static String[][] parseCert(X509Certificate c) { + String version = "" + c.getVersion(); + String serialNumber = String.valueOf(c.getSerialNumber()); + String signatureAlg = c.getSigAlgName(); + String issuer = String.valueOf(c.getIssuerX500Principal()); + String validity = String.valueOf(new CertificateValidity(c.getNotBefore(), c.getNotAfter())); + String subject = String.valueOf(c.getSubjectX500Principal()); + + String signature = c.getSignature() != null ? jdkIndependentHexEncoder(c.getSignature()) : null; + + String md5Hash = ""; + String sha1Hash = ""; + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(c.getEncoded()); + md5Hash = makeFingerprint(digest.digest()); + + digest = MessageDigest.getInstance("SHA-1"); + digest.update(c.getEncoded()); + sha1Hash = makeFingerprint(digest.digest()); + } catch (Exception e) { + //fail quietly + } + + return new String[][]{{TRANSLATOR.translate("Version"), version}, + {TRANSLATOR.translate("SSerial"), serialNumber}, + {TRANSLATOR.translate("SSignatureAlgorithm"), signatureAlg}, + {TRANSLATOR.translate("SIssuer"), issuer}, + {TRANSLATOR.translate("SValidity"), validity}, + {TRANSLATOR.translate("SSubject"), subject}, + {TRANSLATOR.translate("SSignature"), signature}, + {TRANSLATOR.translate("SMD5Fingerprint"), md5Hash}, + {TRANSLATOR.translate("SSHA1Fingerprint"), sha1Hash} + }; + } + + private static String jdkIndependentHexEncoder(byte[] signature) { + try { + return jdkIndependentHexEncoderImpl(signature); + } catch (Exception ex) { + String s = "Failed to encode signature: " + ex.toString(); + LOG.error("Failed to encode signature", ex); + return s; + } + } + + private static String jdkIndependentHexEncoderImpl(byte[] signature) throws Exception { + try { + LOG.debug("trying jdk9's HexDumpEncoder"); + Class clazz = Class.forName("sun.security.util.HexDumpEncoder"); + Object encoder = clazz.newInstance(); + Method m = clazz.getDeclaredMethod("encodeBuffer", byte[].class); + //convert our signature into a nice human-readable form. + return (String) m.invoke(encoder, signature); + } catch (Exception ex) { + LOG.debug("trying jdk8's HexDumpEncoder"); + Class clazz = Class.forName("sun.misc.HexDumpEncoder"); + Object encoder = clazz.newInstance(); + Method m = clazz.getMethod("encode", byte[].class); + //convert our signature into a nice human-readable form. + return (String) m.invoke(encoder, signature); + } + } + + /** + * Makes a human readable hash fingerprint. + * For example: 11:22:33:44:AA:BB:CC:DD:EE:FF. + */ + private static String makeFingerprint(byte[] hash) { + String fingerprint = ""; + for (final byte b : hash) { + if (!fingerprint.equals("")) { + fingerprint += ":"; + } + fingerprint += Integer.toHexString( + ((b & 0xFF) | 0x100)).substring(1, 3); + } + return fingerprint.toUpperCase(); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/LayoutPartsBuilder.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/LayoutPartsBuilder.java new file mode 100644 index 000000000..d500e50ef --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/LayoutPartsBuilder.java @@ -0,0 +1,70 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.panel; + +import net.adoptopenjdk.icedteaweb.StringUtils; +import net.adoptopenjdk.icedteaweb.client.util.gridbag.ComponentRow; +import net.adoptopenjdk.icedteaweb.client.util.gridbag.GridBagRow; +import net.adoptopenjdk.icedteaweb.client.util.gridbag.KeyValueRow; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; +import net.sourceforge.jnlp.JNLPFile; + +import javax.swing.JLabel; +import javax.swing.border.EmptyBorder; +import java.awt.Font; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Optional.ofNullable; +import static net.adoptopenjdk.icedteaweb.ui.ApplicationStyleConstants.PRIMARY_WARNING_COLOR; + +public class LayoutPartsBuilder { + private static final Translator TRANSLATOR = Translator.getInstance(); + + public static List getApplicationDetails(JNLPFile file) { + final List rows = new ArrayList<>(); + + final JLabel applicationSectionTitle = new JLabel(TRANSLATOR.translate("ApplicationDetails")); + applicationSectionTitle.setFont(applicationSectionTitle.getFont().deriveFont(Font.BOLD)); + applicationSectionTitle.setBorder(new EmptyBorder(0, 0, 5, 0)); + + rows.add(new ComponentRow(applicationSectionTitle)); + + if (file.isUnsigend()) { + final JLabel unsignedWarningLabel = new JLabel(TRANSLATOR.translate("SUnverifiedJnlp")); + unsignedWarningLabel.setBorder(new EmptyBorder(0, 0, 5, 0)); + unsignedWarningLabel.setForeground(PRIMARY_WARNING_COLOR); + + rows.add(new ComponentRow(unsignedWarningLabel)); + } + + final String name = ofNullable(file) + .map(JNLPFile::getInformation) + .map(InformationDesc::getTitle) + .orElse(TRANSLATOR.translate("SNoAssociatedCertificate")); + rows.add(new KeyValueRow(TRANSLATOR.translate("Name"), name)); + + final String publisher = ofNullable(file) + .map(JNLPFile::getInformation) + .map(InformationDesc::getVendor) + .map(v -> v + " " + TRANSLATOR.translate("SUnverified")) + .orElse(TRANSLATOR.translate("SNoAssociatedCertificate")); + rows.add(new KeyValueRow(TRANSLATOR.translate("Publisher"), publisher)); + + + final String fromFallback = ofNullable(file) + .map(JNLPFile::getSourceLocation) + .map(URL::getAuthority) + .orElse(""); + + final String from = ofNullable(file) + .map(JNLPFile::getInformation) + .map(InformationDesc::getHomepage) + .map(URL::toString) + .map(i -> !StringUtils.isBlank(i) ? i : null) + .orElse(fromFallback); + rows.add(new KeyValueRow(TRANSLATOR.translate("From"), from)); + return rows; + } + +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/ReferencesPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/ReferencesPanel.java new file mode 100644 index 000000000..bd8e2d469 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/ReferencesPanel.java @@ -0,0 +1,57 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.panel; + +import net.adoptopenjdk.icedteaweb.StringUtils; +import net.adoptopenjdk.icedteaweb.client.util.html.HtmlUtil; +import net.adoptopenjdk.icedteaweb.i18n.Translator; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; + +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import java.awt.BorderLayout; +import java.awt.Desktop; +import java.awt.Font; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Set; + +public class ReferencesPanel extends JPanel { + private static final Logger LOG = LoggerFactory.getLogger(ReferencesPanel.class); + private static final Translator TRANSLATOR = Translator.getInstance(); + + public ReferencesPanel(final String htmlList) { + super(new BorderLayout()); + createContent(null, htmlList); + } + + public ReferencesPanel(final String title, final Set urls) { + super(new BorderLayout()); + createContent(title, HtmlUtil.unorderedListOf(urls, 4)); + } + + private void createContent(final String listTitle, final String htmlList) { + final String html = StringUtils.isBlank(listTitle) ? htmlList : listTitle + "
    " + htmlList; + + final JEditorPane editorPane = new JEditorPane("text/html", html); + editorPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); + editorPane.setEditable(false); + editorPane.setFont(new Font(Font.SANS_SERIF, getFont().getStyle(), getFont().getSize())); + editorPane.setBackground(null); + editorPane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + try { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + Desktop.getDesktop().browse(e.getURL().toURI()); + } + } catch (IOException | URISyntaxException ex) { + LOG.error("Error while trying to open hyperlink from dialog.", e); + } + } + }); + add(editorPane, BorderLayout.CENTER); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/RememberUserDecisionPanel.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/RememberUserDecisionPanel.java new file mode 100644 index 000000000..f3d64b2f8 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/panel/RememberUserDecisionPanel.java @@ -0,0 +1,48 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.panel; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.Remember; +import net.adoptopenjdk.icedteaweb.i18n.Translator; + +import javax.swing.ButtonGroup; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.border.EmptyBorder; +import java.awt.FlowLayout; + +public class RememberUserDecisionPanel extends JPanel { + private static final Translator TRANSLATOR = Translator.getInstance(); + + final JRadioButton forApplicationRadioButton = new JRadioButton(TRANSLATOR.translate("EXAWrememberByApp")); + final JRadioButton forDomainRadioButton = new JRadioButton(TRANSLATOR.translate("EXAWrememberByPage")); + final JRadioButton doNotRememberRadioButton = new JRadioButton(TRANSLATOR.translate("EXAWdontRemember"), true); + + public RememberUserDecisionPanel() { + super(new FlowLayout(FlowLayout.CENTER)); + this.setBorder(new EmptyBorder(0, 0, 0, 0)); + + this.add(forApplicationRadioButton); + this.add(forDomainRadioButton); + this.add(doNotRememberRadioButton); + + forApplicationRadioButton.setToolTipText(TRANSLATOR.translate("EXAWrememberByAppTooltip")); + forDomainRadioButton.setToolTipText(TRANSLATOR.translate("EXAWrememberByPageTooltip")); + doNotRememberRadioButton.setToolTipText(TRANSLATOR.translate("EXAWdontRememberTooltip")); + + final ButtonGroup bg = new ButtonGroup(); + bg.add(forApplicationRadioButton); + bg.add(forDomainRadioButton); + bg.add(doNotRememberRadioButton); + + this.validate(); + } + + public Remember getResult() { + if (forApplicationRadioButton.isSelected()) { + return Remember.REMEMBER_BY_APPLICATION; + } + if (forDomainRadioButton.isSelected()) { + return Remember.REMEMBER_BY_DOMAIN; + } + return Remember.DO_NOT_REMEMBER; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AccessWarningResult.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AccessWarningResult.java new file mode 100644 index 000000000..aa7397746 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AccessWarningResult.java @@ -0,0 +1,5 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.result; + +public enum AccessWarningResult { + SANDBOX, YES, NO, ALWAYS; +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDeny.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDeny.java new file mode 100644 index 000000000..ed25ef098 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDeny.java @@ -0,0 +1,11 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.result; + +import javax.swing.JCheckBox; + +public enum AllowDeny { + ALLOW, DENY; + + public static AllowDeny valueOf(final JCheckBox checkbox) { + return checkbox.isSelected() ? ALLOW : DENY; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDenySandbox.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDenySandbox.java new file mode 100644 index 000000000..e3d5f9e35 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/AllowDenySandbox.java @@ -0,0 +1,5 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.result; + +public enum AllowDenySandbox { + ALLOW, DENY, SANDBOX; +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/CreateShortcutResult.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/CreateShortcutResult.java new file mode 100644 index 000000000..0933240d3 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/CreateShortcutResult.java @@ -0,0 +1,20 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.result; + +public class CreateShortcutResult { + private final AllowDeny createDesktopShortcut; + private final AllowDeny createMenuShortcut; + + + public CreateShortcutResult(final AllowDeny createDesktopShortcut, final AllowDeny createMenuShortcut) { + this.createDesktopShortcut = createDesktopShortcut; + this.createMenuShortcut = createMenuShortcut; + } + + public AllowDeny getCreateDesktopShortcut() { + return createDesktopShortcut; + } + + public AllowDeny getCreateMenuShortcut() { + return createMenuShortcut; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/RememberableResult.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/RememberableResult.java new file mode 100644 index 000000000..e4d352a1d --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/security/dialog/result/RememberableResult.java @@ -0,0 +1,21 @@ +package net.adoptopenjdk.icedteaweb.security.dialog.result; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.Remember; + +public class RememberableResult { + private final T result; + private final Remember remember; + + public RememberableResult(final T result, final Remember remember) { + this.result = result; + this.remember = remember; + } + + public T getResult() { + return result; + } + + public Remember getRemember() { + return remember; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntries.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntries.java new file mode 100644 index 000000000..177399d5e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntries.java @@ -0,0 +1,45 @@ +package net.adoptopenjdk.icedteaweb.userdecision; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +class FileStoreEntries { + + @SuppressWarnings("unused") // this is used for identifying the serialization version in the JSON file. + private final String version = "1"; + private final List userDecisions; + + FileStoreEntries() { + this.userDecisions = new ArrayList<>(); + } + + > Optional getDecision(final URL domain, final Set jarNames, final UserDecision.Key key, final Class resultType) { + final T[] enumConstants = resultType.getEnumConstants(); + return userDecisions.stream() + .filter(entry -> entry.getDomain().equals(domain)) + .filter(entry -> entry.getJarNames().isEmpty() || entry.getJarNames().equals(jarNames)) + .max(Comparator.comparingInt(entry -> entry.getJarNames().size())) + .flatMap(entry -> entry.getUserDecisionValue(key)) + .filter(value -> Arrays.stream(enumConstants).anyMatch((t) -> t.name().equals(value))) + .map(value -> Enum.valueOf(resultType, value)); + } + + > void addDecision(final URL domain, final Set jarNames, final UserDecision userDecision) { + final FileStoreEntry foundEntry = userDecisions.stream() + .filter(entry -> entry.getDomain().equals(domain)) + .filter(entry -> entry.getJarNames().equals(jarNames)) + .findFirst() + .orElseGet(() -> { + final FileStoreEntry newEntry = new FileStoreEntry(domain, jarNames); + userDecisions.add(newEntry); + return newEntry; + }); + + foundEntry.setUserDecision(userDecision); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntry.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntry.java new file mode 100644 index 000000000..64bf88176 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/FileStoreEntry.java @@ -0,0 +1,34 @@ +package net.adoptopenjdk.icedteaweb.userdecision; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +class FileStoreEntry { + private final URL domain; + private final Set jarNames; + private final Map decisions = new HashMap<>(); + + FileStoreEntry(final URL domain, final Set jarNames) { + this.domain = domain; + this.jarNames = jarNames; + } + + Optional getUserDecisionValue(UserDecision.Key key) { + return Optional.ofNullable(decisions.get(key)); + } + + > void setUserDecision(UserDecision userDecision) { + decisions.put(userDecision.getKey(), userDecision.getValue().name()); + } + + URL getDomain() { + return domain; + } + + Set getJarNames() { + return jarNames; + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecision.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecision.java new file mode 100644 index 000000000..38e12910e --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecision.java @@ -0,0 +1,106 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.userdecision; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.sourceforge.jnlp.security.AccessType; + +import java.util.Objects; +import java.util.StringJoiner; +import java.util.stream.Stream; + +public class UserDecision> { + private final Key key; + private final T value; + + public enum Key { + CREATE_DESKTOP_SHORTCUT, + CREATE_MENU_SHORTCUT, + ESTABLISH_NETWORK_CONNECTION(AccessType.NETWORK), + READ_FILE(AccessType.READ_FILE), + WRITE_FILE(AccessType.WRITE_FILE), + READ_WRITE_FILE(AccessType.READ_WRITE_FILE), + READ_CLIPBOARD(AccessType.CLIPBOARD_READ), + WRITE_CLIPBOARD(AccessType.CLIPBOARD_WRITE), + USE_PRINTER(AccessType.PRINTER), + RUN_UNSIGNED_APPLICATION, + RUN_PARTIALLY_APPLICATION, + RUN_MISSING_PERMISSIONS_APPLICATION, + RUN_MISSING_ALAC_APPLICATION, + RUN_MATCHING_ALAC_APPLICATION, + ; + + private final AccessType accessType; + + Key() { + this.accessType = null; + } + + Key(AccessType accessType) { + this.accessType = accessType; + } + + public static Key valueOf(AccessType accessType) { + return Stream.of(Key.values()) + .filter(k -> k.accessType == accessType) + .findFirst() + .orElseThrow(()->new IllegalArgumentException("Could not find key for access type " + accessType)); + + } + } + + private UserDecision(final Key key, final T value) { + this.key = Assert.requireNonNull(key, "key"); + this.value = Assert.requireNonNull(value, "value"); + } + + public static > UserDecision of(final Key key, final T value) { + return new UserDecision(key, value); + } + + public static > UserDecision of(final AccessType accessType, final T value) { + return new UserDecision<>(Key.valueOf(accessType), value); + } + + public Key getKey() { + return key; + } + + public T getValue() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof UserDecision)) return false; + final UserDecision that = (UserDecision) o; + return key == that.key; + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public String toString() { + return new StringJoiner(", ", UserDecision.class.getSimpleName() + "[", "]") + .add("key=" + key) + .add("value=" + value) + .toString(); + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisions.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisions.java new file mode 100644 index 000000000..e82ba1d01 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisions.java @@ -0,0 +1,38 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.userdecision; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.remember.Remember; +import net.sourceforge.jnlp.JNLPFile; + +import java.util.Optional; + +public interface UserDecisions { + > Optional getUserDecisions(UserDecision.Key key, JNLPFile file, Class resultType); + + > void saveForDomain(JNLPFile file, UserDecision userDecision); + + > void saveForApplication(JNLPFile file, UserDecision userDecision); + + default > void save(Remember result, JNLPFile file, UserDecision userDecision) { + if (result == Remember.REMEMBER_BY_DOMAIN) { + saveForDomain(file, userDecision); + } + else if (result == Remember.REMEMBER_BY_APPLICATION) { + saveForApplication(file, userDecision); + } + } +} diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStore.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStore.java new file mode 100644 index 000000000..ff72e4c17 --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStore.java @@ -0,0 +1,164 @@ +// Copyright (C) 2019 Karakun AG +// +// 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.adoptopenjdk.icedteaweb.userdecision; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.adoptopenjdk.icedteaweb.io.FileUtils; +import net.adoptopenjdk.icedteaweb.io.IOUtils; +import net.adoptopenjdk.icedteaweb.lockingfile.LockableFile; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.JNLPFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static net.adoptopenjdk.icedteaweb.Assert.requireNonNull; +import static net.sourceforge.jnlp.config.PathsAndFiles.USER_DECISIONS_FILE_STORE; + +public class UserDecisionsFileStore implements UserDecisions { + + private static final Logger LOG = LoggerFactory.getLogger(UserDecisionsFileStore.class); + + private final LockableFile lockableFile; + private final Gson gson; + private final File store; + + public UserDecisionsFileStore() { + this(USER_DECISIONS_FILE_STORE.getFile()); + } + + // visible for testing + UserDecisionsFileStore(final File store) { + this.store = requireNonNull(store, "store"); + this.lockableFile = LockableFile.getInstance(store); + this.gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + + if (!store.isFile()) { + try { + FileUtils.createRestrictedFile(store); + } catch (IOException ex) { + throw new RuntimeException("Could not create user decisions file store", ex); + } + } + } + + @Override + public > Optional getUserDecisions(final UserDecision.Key key, final JNLPFile file, final Class resultType) { + requireNonNull(file, "file"); + requireNonNull(key, "key"); + + final URL domain = getDomain(file); + final Set jarNames = getJarNames(file); + try { + lockableFile.lock(); + try { + return readJsonFile().getDecision(domain, jarNames, key, resultType); + } finally { + lockableFile.unlock(); + } + } catch (IOException ex) { + LOG.error("Failed to lock/unlock user decisions file store: " + store, ex); + } + + return Optional.empty(); + } + + @Override + public > void saveForDomain(final JNLPFile file, final UserDecision userDecision) { + requireNonNull(file, "file"); + requireNonNull(userDecision, "userDecision"); + + final URL domain = getDomain(file); + save(userDecision, domain, emptySet()); + } + + @Override + public > void saveForApplication(final JNLPFile file, final UserDecision userDecision) { + requireNonNull(file, "file"); + requireNonNull(userDecision, "userDecision"); + + final URL domain = getDomain(file); + final Set jarNames = getJarNames(file); + save(userDecision, domain, jarNames); + } + + private > void save(final UserDecision userDecision, final URL domain, final Set jarNames) { + try { + lockableFile.lock(); + try { + final FileStoreEntries userDecisions = readJsonFile(); + userDecisions.addDecision(domain, jarNames, userDecision); + writeJsonFile(userDecisions); + } finally { + lockableFile.unlock(); + } + } catch (IOException ex) { + LOG.error("Failed to lock/unlock user decisions file store: " + store, ex); + } + } + + private Set getJarNames(final JNLPFile file) { + return file.getResourcesDescs().stream() + .flatMap(resourcesDesc -> Arrays.stream(resourcesDesc.getJARs())) + .map(jarDesc -> new File(jarDesc.getLocation().toString()).getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private URL getDomain(final JNLPFile file) { + final URL codebase = file.getNotNullProbableCodeBase(); + try { + return new URL(codebase.getProtocol(), codebase.getHost(), codebase.getPort(), ""); + } catch (MalformedURLException ex) { + LOG.error(format("Failed to determine domain as codebase '%s' is not a valid URL", codebase), ex); + throw new RuntimeException(ex); + } + } + + private FileStoreEntries readJsonFile() { + try (FileInputStream in = new FileInputStream(store)) { + final InputStreamReader reader = new InputStreamReader(in); + return Optional.ofNullable(gson.fromJson(reader, FileStoreEntries.class)) + .orElse(new FileStoreEntries()); + } catch (IOException ex) { + LOG.error("Could not read from user decisions file store: " + store, ex); + return new FileStoreEntries(); + } + } + + private void writeJsonFile(FileStoreEntries entries) { + try (FileOutputStream out = new FileOutputStream(store)) { + IOUtils.writeUtf8Content(out, gson.toJson(entries)); + } catch (IOException ex) { + LOG.error("Could not write to user decisions file store: " + store, ex); + } + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/JNLPFile.java b/core/src/main/java/net/sourceforge/jnlp/JNLPFile.java index d2c8125eb..24e074085 100644 --- a/core/src/main/java/net/sourceforge/jnlp/JNLPFile.java +++ b/core/src/main/java/net/sourceforge/jnlp/JNLPFile.java @@ -17,7 +17,6 @@ package net.sourceforge.jnlp; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; import net.adoptopenjdk.icedteaweb.JavaSystemProperties; import net.adoptopenjdk.icedteaweb.StringUtils; import net.adoptopenjdk.icedteaweb.jnlp.element.EntryPoint; @@ -26,18 +25,17 @@ import net.adoptopenjdk.icedteaweb.jnlp.element.extension.ComponentDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.extension.InstallerDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JNLPResources; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JREDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PropertyDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.update.UpdateDesc; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionId; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader; +import net.adoptopenjdk.icedteaweb.security.PermissionsManager; import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; import net.adoptopenjdk.icedteaweb.xmlparser.XMLParser; import net.adoptopenjdk.icedteaweb.xmlparser.XmlNode; @@ -46,9 +44,9 @@ import sun.net.www.protocol.http.HttpURLConnection; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.security.PermissionCollection; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -59,6 +57,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static net.adoptopenjdk.icedteaweb.JavaSystemPropertiesConstants.HTTP_AGENT; import static net.adoptopenjdk.icedteaweb.StringUtils.hasPrefixMatch; @@ -96,12 +95,6 @@ public class JNLPFile { public static final String SPEC_VERSION_DEFAULT = "1.0+"; - // todo: save the update policy, then if file was not updated - // then do not check resources for being updated. - // - // todo: currently does not filter resources by jvm version. - // - /** * the location this JNLP file was created from */ @@ -149,7 +142,7 @@ public class JNLPFile { /** * resources */ - protected List resources; + protected JNLPResources resources; /** * additional resources not in JNLP file (from command line) @@ -171,6 +164,11 @@ public class JNLPFile { */ protected SecurityDesc security; + /** + * the default Java version + */ + protected String defaultJavaVersion = null; + /** * the default JVM locale */ @@ -189,34 +187,23 @@ public class JNLPFile { /** * A signed JNLP file is missing from the main jar */ - private boolean missingSignedJNLP = false; - - /** - * JNLP file contains special properties - */ - private boolean containsSpecialProperties = false; + private boolean isSigned = false; /** * List of acceptable properties (not-special) */ - final private String[] generalProperties = SecurityDesc.getJnlpRIAPermissions(); - - /** - * important manifests' attributes - */ - private final ManifestAttributesReader manifestAttributesReader = new ManifestAttributesReader(this); + private final PermissionCollection generalProperties = PermissionsManager.getJnlpRiaPermissions(); - private static final String TITLE_NOT_FOUND = "Application title was not found in manifest. Check with application vendor"; private static final String FAKE_TITLE = "Corrupted or missing title. Do not trust this application!"; { // initialize defaults if security allows try { defaultLocale = Locale.getDefault(); + defaultJavaVersion = JavaSystemProperties.getJavaVersion(); defaultOS = JavaSystemProperties.getOsName(); defaultArch = JavaSystemProperties.getOsArch(); - } - catch (SecurityException ex) { + } catch (SecurityException ex) { // FIXME: how should we proceed if the default values are not available?? } } @@ -225,34 +212,20 @@ public class JNLPFile { * Empty stub, allowing child classes to override the constructor */ // only used for tests - protected JNLPFile() { + public JNLPFile() { this.parserSettings = null; this.fileLocation = null; this.uniqueKey = 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 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 - */ - JNLPFile(final InputStream input, - final URL location, - final ParserSettings settings, - final String uniqueKey - ) throws IOException, ParseException { + public JNLPFile(final InputStream input, final URL location, final URL codebase, final ParserSettings settings, final String uniqueKey) throws ParseException { this.parserSettings = settings; this.fileLocation = location; this.uniqueKey = uniqueKey; - - parse(input, location, null); + parse(input, location, codebase); final String httpAgent = getResources().getPropertiesMap().get(HTTP_AGENT); - if (! StringUtils.isBlank(httpAgent)) { + if (!StringUtils.isBlank(httpAgent)) { System.setProperty(HTTP_AGENT, httpAgent); if (!HttpURLConnection.userAgent.contains(httpAgent)) { LOG.warn("Cannot set HTTP User-Agent as a connection has been opened before reading the JNLP file"); @@ -260,35 +233,6 @@ protected JNLPFile() { } } - /** - * Create a JNLPFile from an input stream. - * - * @param input input stream from which create jnlp file - * @param settings settings of parser - * @throws ParseException if the JNLP file was invalid - */ - // only used for tests - public JNLPFile(final InputStream input, final ParserSettings settings) throws ParseException { - this(input, null, settings); - } - - /** - * 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 settings the {@link ParserSettings} to use when parsing - * @throws ParseException if the JNLP file was invalid - */ - // only used for tests - public JNLPFile(final InputStream input, final URL codebase, final ParserSettings settings) throws ParseException { - this.parserSettings = settings; - this.fileLocation = null; - this.uniqueKey = null; - parse(input, null, codebase); - } - - /** * @return the JNLP file's best localized title. This method returns the same * value as InformationDesc.getTitle(). @@ -299,14 +243,13 @@ public JNLPFile(final InputStream input, final URL codebase, final ParserSetting public String getTitle() { try { return getTitle(false); - } - catch (MissingTitleException cause) { + } catch (MissingTitleException cause) { throw new RuntimeException(cause); } } public String getTitle(boolean kill) throws MissingTitleException { - final String title = getTitleImpl(); + final String title = getTitleFromJnlp(); if (StringUtils.isBlank(title)) { LOG.warn("The title section has not been specified for your locale nor does a default value exist in the JNLP file. and Missing Title"); @@ -320,25 +263,6 @@ public String getTitle(boolean kill) throws MissingTitleException { return title; } - private String getTitleImpl() { - 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 = getManifestAttributesReader().getMainClass(); - return mainClass; - } - /** * @return the JNLP file's best localized title. This method returns the * same value as InformationDesc.getTitle(). @@ -347,14 +271,6 @@ public String getTitleFromJnlp() { return getInformation().getTitle(); } - public String getTitleFromManifest() { - String inManifestTitle = getManifestAttributesReader().getApplicationName(); - if (inManifestTitle == null && getManifestAttributesReader().isLoader()) { - LOG.warn(TITLE_NOT_FOUND); - } - return inManifestTitle; - } - /** * @return the JNLP file's best localized vendor. This method returns the * same value as InformationDesc.getVendor(). @@ -362,8 +278,7 @@ public String getTitleFromManifest() { public String getVendor() { try { return getVendor(false); - } - catch (MissingVendorException cause) { + } catch (MissingVendorException cause) { throw new RuntimeException(cause); } } @@ -380,8 +295,7 @@ public String getVendor(boolean kill) throws MissingVendorException { LOG.warn("The vendor section has not been specified for your locale nor does a default value exist in the JNLP file."); vendor = "Corrupted or missing vendor. Do not trust this application!"; LOG.warn("However there is to many applications known to suffer this issue, so providing fake:" + "vendor" + ": " + vendor); - } - else { + } else { LOG.info("Acceptable vendor tag found, contains: {}", vendor); } return vendor; @@ -458,9 +372,8 @@ public URL getNotNullProbableCodeBase() { } try { return UrlUtils.removeFileName(getSourceLocation()); - } - catch (Exception ex) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, ex); + } catch (Exception ex) { + LOG.error("Exception while removing file name from URL", ex); } return getSourceLocation(); } @@ -500,7 +413,7 @@ public InformationDesc getInformation(final Locale locale, final String os, fina final Map> mergedItems = new HashMap<>(); infos.stream() - .filter(infoDesc -> localeMatches(infoDesc.getLocales(), locale)) + .filter(infoDesc -> localeMatches(locale, infoDesc.getLocales())) .filter(infoDesc -> hasPrefixMatch(os, infoDesc.getOs())) .filter(infoDesc -> hasPrefixMatch(arch, infoDesc.getArch())) .peek(infoDesc -> { @@ -511,7 +424,7 @@ public InformationDesc getInformation(final Locale locale, final String os, fina .flatMap(infoDesc -> infoDesc.getItems().entrySet().stream()) .forEach(itemEntry -> { final List newValues = itemEntry.getValue().stream() - .filter(v -> v != null) + .filter(Objects::nonNull) .filter(v -> !StringUtils.isBlank(v.toString())) .collect(toList()); @@ -522,7 +435,7 @@ public InformationDesc getInformation(final Locale locale, final String os, fina @Override public List getItems(String key) { final List result = mergedItems.get(key); - return result == null ? Collections.emptyList() : result; + return result == null ? emptyList() : result; } @Override @@ -549,15 +462,8 @@ public SecurityDesc getSecurity() { /** * @return the requested security level of the application represented by this JNLP file. */ - public ApplicationPermissionLevel getApplicationPermissionLevel() { - return this.security.getApplicationPermissionLevel(); - } - - /** - * @return the requested security level of the applet represented by this JNLP file. - */ - public AppletPermissionLevel getAppletPermissionLevel() { - return this.security.getAppletPermissionLevel(); + public ApplicationEnvironment getApplicationEnvironment() { + return this.security.getApplicationEnvironment(); } /** @@ -566,22 +472,11 @@ public AppletPermissionLevel getAppletPermissionLevel() { * properties. */ public ResourcesDesc getResources() { - return getResources(defaultLocale, defaultOS, defaultArch); - } - - /** - * @param locale preferred locale of resource - * @param os preferred os of resource - * @param arch preferred arch of resource - * @return the resources section of the JNLP file for the - * specified locale, os, and arch. - */ - public ResourcesDesc getResources(final Locale locale, final String os, final String arch) { - return new ResourcesDesc(this, new Locale[]{locale}, new String[]{os}, new String[]{arch}) { + return new ResourcesDesc(this, new Locale[]{defaultLocale}, new String[]{defaultOS}, new String[]{defaultArch}) { @Override public List getResources(Class launchType) { - final List result = getResourcesDescs(locale, os, arch).stream() + final List result = getResourcesDescs().stream() .flatMap(resDesc -> resDesc.getResources(launchType).stream()) .collect(toList()); @@ -590,14 +485,20 @@ public List getResources(Class launchType) { return result; } + /** + * Only called from Launcher to add propery descriptions from command line. + */ @Override public void addResource(Object resource) { - // todo: honor the current locale, os, arch values sharedResources.addResource(resource); } }; } + public JNLPResources getJnlpResources() { + return new JNLPResources(getResourcesDescs()); + } + /** * @return the resources section of the JNLP file as viewed * through the default locale and the os.name and os.arch @@ -605,16 +506,32 @@ public void addResource(Object resource) { * XXX: Before overriding this method or changing its implementation, * read the comment in JNLPFile.getDownloadOptionsForJar(JARDesc). */ - public ResourcesDesc[] getResourcesDescs() { - return getResourcesDescs(defaultLocale, defaultOS, defaultArch).toArray(new ResourcesDesc[0]); + public List getResourcesDescs() { + final JNLPResources resourcesOutsideOfJreDesc = getResourcesOutsideOfJreDesc(); + final List jreResources = getResourcesOfJreDesc(resourcesOutsideOfJreDesc); + + final List result = new ArrayList<>(); + result.addAll(resourcesOutsideOfJreDesc.all()); + result.addAll(jreResources); + return result; } - private List getResourcesDescs(Locale locale, String os, String arch) { - return resources.stream() - .filter(rescDesc -> hasPrefixMatch(os, rescDesc.getOS())) - .filter(rescDesc -> hasPrefixMatch(arch, rescDesc.getArch())) - .filter(rescDesc -> localeMatches(rescDesc.getLocales(), locale)) - .collect(toList()); + private JNLPResources getResourcesOutsideOfJreDesc() { + return resources.filterResources(defaultLocale, defaultOS, defaultArch); + } + + private List getResourcesOfJreDesc(JNLPResources resourcesOutsideOfJreDesc) { + final List jres = resourcesOutsideOfJreDesc.getJREs(); + if (jres.isEmpty()) { + return emptyList(); + } + return jres.stream() + .filter(jreDesc -> jreDesc.getVersion().contains(defaultJavaVersion)) + .findFirst() + .map(JREDesc::getJnlpResources) + .map(jnlpResources -> jnlpResources.filterResources(defaultLocale, defaultOS, defaultArch)) + .map(JNLPResources::all) + .orElse(emptyList()); } /** @@ -697,22 +614,6 @@ public boolean isInstaller() { return entryPointDesc instanceof InstallerDesc; } - /** - * Sets the default view of the JNLP file returned by - * getInformation, getResources, etc. If unset, the defaults - * are the properties os.name, os.arch, and the locale returned - * by Locale.getDefault(). - * - * @param os preferred os of resource - * @param arch preferred arch of resource - * @param locale preferred locale of resource - */ - public void setDefaults(String os, String arch, Locale locale) { - defaultOS = os; - defaultArch = arch; - defaultLocale = locale; - } - /** * Initialize the JNLPFile fields. Private because it's called * from the constructor. @@ -736,19 +637,17 @@ private void parse(InputStream input, URL location, URL forceCodebase) throws Pa infos = parser.getInformationDescs(root); parser.checkForInformation(); update = parser.getUpdate(root); - resources = parser.getResources(root, false); // false == not a j2se/java resources section + resources = new JNLPResources(parser.getResources(root, false)); // false == not a j2se/java resources section entryPointDesc = parser.getEntryPointDesc(root); component = parser.getComponent(root); security = parser.getSecurity(root); checkForSpecialProperties(); - } - catch (ParseException ex) { + } catch (ParseException ex) { throw ex; - } - catch (Exception ex) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, ex); + } catch (Exception ex) { + LOG.error("Exception while parsing JNLP file", ex); throw new RuntimeException(ex.toString()); } } @@ -756,26 +655,10 @@ private void parse(InputStream input, URL location, URL forceCodebase) throws Pa /** * Inspects the JNLP file to check if it contains any special properties */ - private void checkForSpecialProperties() { - - for (ResourcesDesc res : resources) { - for (PropertyDesc propertyDesc : res.getProperties()) { - - for (int i = 0; i < generalProperties.length; i++) { - String property = propertyDesc.getKey(); + private boolean checkForSpecialProperties() { + final Map props = getJnlpResources().getPropertiesMap(); - if (property.equals(generalProperties[i])) { - break; - } - else if (!property.equals(generalProperties[i]) - && i == generalProperties.length - 1) { - containsSpecialProperties = true; - return; - } - } - - } - } + return Collections.list(generalProperties.elements()).stream().anyMatch(gp -> !props.containsKey(gp.getName())); } /** @@ -809,15 +692,9 @@ public List getNewVMArgs() { * @return the download options to use for downloading jars listed in this jnlp file. */ public DownloadOptions getDownloadOptions() { - boolean usePack = false; - boolean useVersion = false; - ResourcesDesc desc = getResources(); - if (Boolean.valueOf(desc.getPropertiesMap().get("jnlp.packEnabled"))) { - usePack = true; - } - if (Boolean.valueOf(desc.getPropertiesMap().get("jnlp.versionEnabled"))) { - useVersion = true; - } + final ResourcesDesc desc = getResources(); + final boolean usePack = Boolean.parseBoolean(desc.getPropertiesMap().get("jnlp.packEnabled")); + final boolean useVersion = Boolean.parseBoolean(desc.getPropertiesMap().get("jnlp.versionEnabled")); return new DownloadOptions(usePack, useVersion); } @@ -828,18 +705,21 @@ public DownloadOptions getDownloadOptions() { * @return true if a warning should be displayed; otherwise false */ public boolean requiresSignedJNLPWarning() { - return (missingSignedJNLP && containsSpecialProperties); + return (isUnsigend() && checkForSpecialProperties()); } /** - * Informs that a signed JNLP file is missing in the main jar + * Marks this file as signed. I.e. the file is matched by an entry in the main jar. */ - public void setSignedJNLPAsMissing() { - missingSignedJNLP = true; + public void markFileAsSigned() { + isSigned = true; } - public ManifestAttributesReader getManifestAttributesReader() { - return manifestAttributesReader; + /** + * Informs that a signed JNLP file is missing in the main jar + */ + public boolean isUnsigend() { + return !isSigned; } private String createShortcutNameFromLocation() { diff --git a/core/src/main/java/net/sourceforge/jnlp/JNLPFileFactory.java b/core/src/main/java/net/sourceforge/jnlp/JNLPFileFactory.java index 952ad1516..f50929bdb 100644 --- a/core/src/main/java/net/sourceforge/jnlp/JNLPFileFactory.java +++ b/core/src/main/java/net/sourceforge/jnlp/JNLPFileFactory.java @@ -24,6 +24,7 @@ import net.adoptopenjdk.icedteaweb.Assert; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTracker; import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; @@ -63,6 +64,21 @@ public JNLPFile create(final URL location, final ParserSettings settings) throws return create(location, uniqueKey, null, settings, JNLPRuntime.getDefaultUpdatePolicy()); } + /** + * Create a JNLPFile from a URL 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 create(final URL location, final VersionString version, final ParserSettings settings) throws IOException, ParseException { + final String uniqueKey = Calendar.getInstance().getTimeInMillis() + "-" + ((int) (Math.random() * Integer.MAX_VALUE)) + "-" + location; + return create(location, uniqueKey, version, settings, JNLPRuntime.getDefaultUpdatePolicy()); + } + /** * Create a JNLPFile from a URL, parent URLm a version and checking for * updates using the specified policy. @@ -77,7 +93,7 @@ public JNLPFile create(final URL location, final ParserSettings settings) throws */ public JNLPFile create(final URL location, final String uniqueKey, final VersionString version, final ParserSettings settings, final UpdatePolicy policy) throws IOException, ParseException { try (InputStream input = openURL(location, version, policy)) { - return new JNLPFile(input, location, settings, uniqueKey); + return new JNLPFile(input, location, null, settings, uniqueKey); } } @@ -97,7 +113,7 @@ private InputStream openURL(final URL location, final VersionString version, fin Assert.requireNonNull(policy, "policy"); try { - final ResourceTracker tracker = new ResourceTracker(false, DownloadOptions.NONE, policy); // no prefetch + final ResourceTracker tracker = new DefaultResourceTracker(false, DownloadOptions.NONE, policy); // no prefetch tracker.addResource(location, version); final File f = tracker.getCacheFile(location); return new FileInputStream(f); @@ -107,4 +123,5 @@ private InputStream openURL(final URL location, final VersionString version, fin throw new IOException(ex); } } + } diff --git a/core/src/main/java/net/sourceforge/jnlp/JNLPMatcher.java b/core/src/main/java/net/sourceforge/jnlp/JNLPMatcher.java index cc3aff4bf..9a8f5a1e8 100644 --- a/core/src/main/java/net/sourceforge/jnlp/JNLPMatcher.java +++ b/core/src/main/java/net/sourceforge/jnlp/JNLPMatcher.java @@ -34,19 +34,22 @@ package net.sourceforge.jnlp; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.xmlparser.XMLParser; import net.adoptopenjdk.icedteaweb.xmlparser.XmlNode; import net.adoptopenjdk.icedteaweb.xmlparser.XmlParserFactory; +import net.sourceforge.jnlp.util.JarFile; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.jar.JarEntry; /** * To compare launching JNLP file with signed APPLICATION.JNLP or @@ -59,43 +62,25 @@ public final class JNLPMatcher { private static final Logger LOG = LoggerFactory.getLogger(JNLPMatcher.class); - private final XmlNode appTemplateNode; - private final XmlNode launchJNLPNode; - private final boolean isTemplate; + static final String TEMPLATE = "JNLP-INF/APPLICATION_TEMPLATE.JNLP"; + static final String APPLICATION = "JNLP-INF/APPLICATION.JNLP"; + + private final File mainJarFile; + private final File jnlpFile; + private final ParserSettings p; /** * Public constructor * - * @param appTemplate the reader stream of the signed APPLICATION.jnlp or + * @param mainJarFile the reader stream of the signed APPLICATION.jnlp or * APPLICATION_TEMPLATE.jnlp - * @param launchJNLP the reader stream of the launching JNLP file - * @param isTemplate a boolean that specifies if appTemplateFile is a template - * @param p settings of parser - * @throws JNLPMatcherException if IOException, XMLParseException is thrown during parsing; - * Or launchJNLP/appTemplate is null + * @param jnlpFile the reader stream of the launching JNLP file + * @param p the parser settings for the JNLP parsing */ - public JNLPMatcher(InputStream appTemplate, InputStream launchJNLP, - boolean isTemplate, ParserSettings p) throws JNLPMatcherException { - - if (appTemplate == null && launchJNLP == null) - throw new JNLPMatcherException("Template JNLP file and Launching JNLP file are both null."); - else if (appTemplate == null) - throw new JNLPMatcherException("Template JNLP file is null."); - else if (launchJNLP == null) - throw new JNLPMatcherException("Launching JNLP file is null."); - - try { - final XMLParser xmlParser = XmlParserFactory.getParser(p.getParserType()); - this.appTemplateNode = xmlParser.getRootNode(appTemplate); - this.launchJNLPNode = xmlParser.getRootNode(launchJNLP); - this.isTemplate = isTemplate; - } catch (Exception e) { - throw new JNLPMatcherException("Failed to create an instance of JNLPVerify with specified InputStreamReader", e); - } finally { - closeInputStream(appTemplate); - closeInputStream(launchJNLP); - - } + public JNLPMatcher(File mainJarFile, File jnlpFile, ParserSettings p) { + this.mainJarFile = mainJarFile; + this.jnlpFile = jnlpFile; + this.p = p; } /** @@ -104,9 +89,60 @@ else if (launchJNLP == null) * @return true if both JNLP files are 'matched', otherwise false */ public boolean isMatch() { + try (final JarFile jarFile = new JarFile(mainJarFile)) { + for (JarEntry entry : Collections.list(jarFile.entries())) { + final String entryName = entry.getName().toUpperCase(); + + if (entryName.equals(APPLICATION)) { + LOG.debug("APPLICATION.JNLP has been located within signed JAR."); + return isMatch(jarFile, entry, false); + } else if (entryName.equals(TEMPLATE)) { + LOG.debug("APPLICATION_TEMPLATE.JNLP has been located within signed JAR. Starting verification..."); + return isMatch(jarFile, entry, true); + } + } + } catch (IOException e) { + LOG.error("Could not read local main jar file: {}", e.getMessage()); + } + return false; + } + + private boolean isMatch(JarFile jarFile, JarEntry entry, boolean isTemplate) { + try (final InputStream jnlpStream = jarFile.getInputStream(entry)) { + return isMatch(jnlpStream, isTemplate); + } catch (IOException e) { + LOG.error("Could not read JNLP jar entry: {}", e.getMessage()); + return false; + } + } + + private boolean isMatch(final InputStream appTemplateStream, final boolean isTemplate) { + try (final InputStream launchJNLPStream = new FileInputStream(jnlpFile)) { + return isMatch(appTemplateStream, launchJNLPStream, isTemplate); + } catch (IOException e) { + LOG.error("Could not read local JNLP file: {}", e.getMessage()); + return false; + } + } - return matchNodes(appTemplateNode, launchJNLPNode); + private boolean isMatch(final InputStream appTemplateStream, final InputStream launchJNLPStream, final boolean isTemplate) { + try { + final XMLParser xmlParser = XmlParserFactory.getParser(p.getParserType()); + final XmlNode appTemplateNode = xmlParser.getRootNode(appTemplateStream); + final XmlNode launchJNLPNode = xmlParser.getRootNode(launchJNLPStream); + final boolean result = matchNodes(appTemplateNode, launchJNLPNode, isTemplate); + + if (result) { + LOG.debug("JNLP file verification successful"); + } else { + LOG.warn("Signed JNLP file in main jar does not match launching JNLP file"); + } + return result; + } catch (Exception e) { + LOG.error("Failed to create an instance of JNLPVerify with specified InputStreamReader: {}", e.getMessage()); + return false; + } } /** @@ -116,18 +152,16 @@ public boolean isMatch() { * @param launchJNLP launching JNLP file's Node * @return true if both Nodes are 'matched', otherwise false */ - private boolean matchNodes(XmlNode appTemplate, XmlNode launchJNLP) { + private boolean matchNodes(XmlNode appTemplate, XmlNode launchJNLP, final boolean isTemplate) { if (appTemplate != null && launchJNLP != null) { - XmlNode templateNode = appTemplate; - XmlNode launchNode = launchJNLP; // Store children of Node - List appTemplateChild = new LinkedList<>(Arrays.asList(templateNode.getChildNodes())); - List launchJNLPChild = new LinkedList<>(Arrays.asList(launchNode.getChildNodes())); + List appTemplateChild = new LinkedList<>(Arrays.asList(appTemplate.getChildNodes())); + List launchJNLPChild = new LinkedList<>(Arrays.asList(launchJNLP.getChildNodes())); // Compare only if both Nodes have the same name, else return false - if (templateNode.getNodeName().equals(launchNode.getNodeName())) { + if (appTemplate.getNodeName().equals(launchJNLP.getNodeName())) { if (appTemplateChild.size() == launchJNLPChild.size()) { // Compare // children @@ -136,7 +170,7 @@ private boolean matchNodes(XmlNode appTemplate, XmlNode launchJNLP) { for (int i = 0; i < childLength; ) { for (int j = 0; j < childLength; j++) { - boolean isSame = matchNodes(appTemplateChild.get(i), launchJNLPChild.get(j)); + boolean isSame = matchNodes(appTemplateChild.get(i), launchJNLPChild.get(j), isTemplate); if (!isSame && j == childLength - 1) { return false; } else if (isSame) { // If both child matches, remove them from the list of children @@ -148,10 +182,10 @@ private boolean matchNodes(XmlNode appTemplate, XmlNode launchJNLP) { } } - if (!templateNode.getNodeValue().equals(launchNode.getNodeValue())) { + if (!appTemplate.getNodeValue().equals(launchJNLP.getNodeValue())) { // If it's a template and the template's value is NOT '*' - if (isTemplate && !templateNode.getNodeValue().equals("*")) { + if (isTemplate && !appTemplate.getNodeValue().equals("*")) { return false; } // Else if it's not a template, then return false @@ -160,7 +194,7 @@ else if (!isTemplate) { } } // Compare attributes of both Nodes - return matchAttributes(templateNode, launchNode); + return matchAttributes(appTemplate, launchJNLP, isTemplate); } } @@ -175,7 +209,7 @@ else if (!isTemplate) { * @param launchNode launching JNLP file's {@link XmlNode} with attributes * @return {@code true} if both {@link XmlNode Nodes} have 'matched' attributes, otherwise {@code false} */ - private boolean matchAttributes(XmlNode templateNode, XmlNode launchNode) { + private boolean matchAttributes(XmlNode templateNode, XmlNode launchNode, boolean isTemplate) { if (templateNode != null && launchNode != null) { @@ -213,34 +247,4 @@ private boolean matchAttributes(XmlNode templateNode, XmlNode launchNode) { } return false; } - - /*** - * Closes an input stream - * - * @param stream - * The input stream that will be closed - */ - private void closeInputStream(InputStream stream) { - if (stream != null) - try { - stream.close(); - } catch (Exception e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); - } - } - - /*** - * Closes an output stream - * - * @param stream - * The output stream that will be closed - */ - private void closeOutputStream(OutputStream stream) { - if (stream != null) - try { - stream.close(); - } catch (Exception e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); - } - } } diff --git a/core/src/main/java/net/sourceforge/jnlp/Launcher.java b/core/src/main/java/net/sourceforge/jnlp/Launcher.java index 91fb8bc19..90ef4d531 100644 --- a/core/src/main/java/net/sourceforge/jnlp/Launcher.java +++ b/core/src/main/java/net/sourceforge/jnlp/Launcher.java @@ -29,17 +29,14 @@ import net.sourceforge.jnlp.config.DeploymentConfiguration; import net.sourceforge.jnlp.runtime.AppContextFactory; import net.sourceforge.jnlp.runtime.AppletInstance; +import net.sourceforge.jnlp.runtime.ApplicationExecutor; import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.runtime.classloader.DelegatingClassLoader; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; import net.sourceforge.jnlp.services.InstanceExistsException; import net.sourceforge.jnlp.services.ServiceUtil; import javax.swing.text.html.parser.ParserDelegator; import java.applet.Applet; -import java.applet.AppletStub; -import java.awt.Container; import java.awt.SplashScreen; import java.io.File; import java.lang.reflect.Method; @@ -48,10 +45,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; import static net.sourceforge.jnlp.LaunchException.FATAL; -import static net.sourceforge.jnlp.LaunchException.MINOR; import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; /** @@ -86,6 +83,8 @@ public class Launcher { private Map> extra = null; + private final ApplicationExecutor applicationExecutor = new ApplicationExecutor(); + /** * @param settings the parser settings to use when the Launcher initiates parsing of * a JNLP file. @@ -115,21 +114,6 @@ public void setInformationToMerge(Map> input) { * @throws LaunchException if an error occurred while launching (also sent to handler) */ public ApplicationInstance launch(final JNLPFile file) throws LaunchException { - return launch(file, null); - } - - /** - * Launches a JNLP file inside the given container if it is an applet. Specifying a - * container has no effect for Applications and Installers. - * - * @param file the JNLP file to launch - * @param cont the container in which to place the application, if it is an applet - * @return the application instance - * @throws LaunchException if an error occurred while launching (also sent to handler) - */ - public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchException { - TgThread tg; - mergeExtraInformation(file, extra); JNLPRuntime.markNetxRunning(); @@ -152,28 +136,49 @@ public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchEx } } - tg = new TgThread(file, cont); - tg.start(); - + final String applicationTitle = file.getTitle(); + final CompletableFuture cf = applicationExecutor.execute(applicationTitle, () -> launchApplicationInstance(file)); try { - tg.join(); - } catch (InterruptedException ex) { - //By default, null is thrown here, and the message dialog is shown. + final ApplicationInstance applicationInstance = cf.join(); if (handler != null) { - handler.handleLaunchWarning(new LaunchException(file, ex, MINOR, "System Error", "Thread interrupted while waiting for file to launch.", "This can lead to deadlock or yield other damage during execution. Please restart your application/browser.")); + handler.launchCompleted(applicationInstance); } - throw new RuntimeException(ex); + return applicationInstance; + } catch (Exception ex) { + LOG.error("Could not launch application instance", ex); + throw new LaunchException("Could not launch application instance", ex); } + } - if (tg.getException() != null) { - throw tg.getException(); - } // passed to handler when first created + private ApplicationInstance launchApplicationInstance(final JNLPFile file) { + try { + // Do not create new AppContext if we're using NetX and icedteaplugin. + // The plugin needs an AppContext too, but it has to be created earlier. + AppContextFactory.createNewAppContext(); - if (handler != null) { - handler.launchCompleted(tg.getApplication()); - } + doPerApplicationAppContextHacks(); - return tg.getApplication(); + final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + + if (file.isApplication()) { + return launchApplication(file, threadGroup); + } else if (file.isApplet()) { + return launchApplet(file, threadGroup); + } else if (file.isInstaller()) { + return launchInstaller(file); + } else { + throw launchError(new LaunchException(file, null, + FATAL, "Application Error", "Not a launchable JNLP file.", + "File must be a JNLP application, applet, or installer type.")); + } + } catch (LaunchException ex) { + LOG.error("Launch exception", ex); + // Exit if we can't launch the application. + JNLPRuntime.exit(1); + } catch (Throwable ex) { + throw new RuntimeException("Error while starting application", ex); + } + return null; } @@ -182,9 +187,9 @@ public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchEx * appropriate file type. * * @param location the URL of the JNLP file to launch - * location to get the pristine version - * @throws LaunchException if there was an exception + * location to get the pristine version * @return the application instance + * @throws LaunchException if there was an exception */ public ApplicationInstance launch(URL location) throws LaunchException { JNLPRuntime.saveHistory(location.toExternalForm()); @@ -194,10 +199,10 @@ public ApplicationInstance launch(URL location) throws LaunchException { /** * Merges extra information into the jnlp file * - * @param file the JNLPFile + * @param file the JNLPFile * @param extra extra information to merge into the JNLP file * @throws LaunchException if an exception occurs while extracting - * extra information + * extra information */ private void mergeExtraInformation(JNLPFile file, Map> extra) throws LaunchException { if (extra == null) { @@ -222,15 +227,16 @@ private void mergeExtraInformation(JNLPFile file, Map> extr /** * Add the properties to the JNLP file. + * * @throws LaunchException if an exception occurs while extracting - * extra information + * extra information */ private void addProperties(JNLPFile file, List props) throws LaunchException { ResourcesDesc resources = file.getResources(); for (String input : props) { - try{ + try { resources.addResource(PropertyDesc.fromString(input)); - }catch (LaunchException ex){ + } catch (LaunchException ex) { throw launchError(ex); } } @@ -239,8 +245,9 @@ private void addProperties(JNLPFile file, List props) throws LaunchExcep /** * Add the params to the JNLP file; only call if file is * actually an applet file. + * * @throws LaunchException if an exception occurs while extracting - * extra information + * extra information */ private void addParameters(JNLPFile file, List params) throws LaunchException { AppletDesc applet = file.getApplet(); @@ -266,7 +273,7 @@ private void addParameters(JNLPFile file, List params) throws LaunchExce private void addArguments(JNLPFile file, List args) { ApplicationDesc app = file.getApplication(); - for (String input : args ) { + for (String input : args) { app.addArgument(input); } } @@ -274,7 +281,8 @@ private void addArguments(JNLPFile file, List args) { /** * Launches the JNLP file at the specified location in a new JVM * instance. All streams are properly redirected. - * @param file the JNLP file to read arguments and JVM details from + * + * @param file the JNLP file to read arguments and JVM details from * @param javawsArgs the arguments to pass to javaws (aka Netx) * @throws LaunchException if there was an exception */ @@ -332,14 +340,16 @@ private JNLPFile fromUrl(URL location) throws LaunchException { } } - /** + /** * Launches a JNLP application. This method should be called * from a thread in the application's thread group. + * * @param file jnlpfile - source of application * @return application to be launched * @throws net.sourceforge.jnlp.LaunchException if launch fails on unrecoverable exception */ - private ApplicationInstance launchApplication(final JNLPFile file) throws LaunchException { + private ApplicationInstance launchApplication(final JNLPFile file, final ThreadGroup threadGroup) throws + LaunchException { if (!file.isApplication()) { throw launchError(new LaunchException(file, null, FATAL, "Application Error", "Not an application file.", "An attempt was made to load a non-application file as an application.")); } @@ -354,9 +364,9 @@ private ApplicationInstance launchApplication(final JNLPFile file) throws Launch } if (JNLPRuntime.getForksStrategy().needsToFork(file)) { - if (!JNLPRuntime.isHeadless()){ + if (!JNLPRuntime.isHeadless()) { SplashScreen sp = SplashScreen.getSplashScreen(); - if (sp!=null) { + if (sp != null) { sp.close(); } } @@ -369,7 +379,7 @@ private ApplicationInstance launchApplication(final JNLPFile file) throws Launch handler.launchInitialized(file); - final ApplicationInstance app = createApplication(file); + final ApplicationInstance app = createApplication(file, threadGroup); app.initialize(); final String mainName = app.getMainClassName(); @@ -389,7 +399,8 @@ private ApplicationInstance launchApplication(final JNLPFile file) throws Launch // create EDT within application context: // dummy method to force Event Dispatch Thread creation - SwingUtils.callOnAppContext(() -> {}); + SwingUtils.callOnAppContext(() -> { + }); setContextClassLoaderForAllThreads(app.getThreadGroup(), app.getClassLoader()); @@ -398,7 +409,7 @@ private ApplicationInstance launchApplication(final JNLPFile file) throws Launch main.setAccessible(true); LOG.info("Invoking main() with args: {}", Arrays.toString(args)); - main.invoke(null, new Object[] { args }); + main.invoke(null, new Object[]{args}); LOG.info("main completed"); return app; @@ -416,7 +427,7 @@ private ApplicationInstance launchApplication(final JNLPFile file) throws Launch * may ask the swing thread to load resources from their JNLP, which * would only work if the Swing thread knows about the JNLPClassLoader. * - * @param tg The threadgroup for which the context classloader should be set + * @param tg The threadgroup for which the context classloader should be set * @param classLoader the classloader to set as the context classloader */ private void setContextClassLoaderForAllThreads(ThreadGroup tg, ClassLoader classLoader) { @@ -436,10 +447,6 @@ private void setContextClassLoaderForAllThreads(ThreadGroup tg, ClassLoader clas thread.setContextClassLoader(classLoader); } } - - //inject classloader into edt delegating classloader - DelegatingClassLoader.getInstance().setClassLoader(classLoader); - } /** @@ -456,11 +463,10 @@ private void setContextClassLoaderForAllThreads(ThreadGroup tg, ClassLoader clas *

    * * @param file the JNLP file - * @param cont container where to put application * @return application * @throws net.sourceforge.jnlp.LaunchException if deploy unrecoverably die */ - private ApplicationInstance launchApplet(final JNLPFile file, final Container cont) throws LaunchException { + private ApplicationInstance launchApplet(final JNLPFile file, final ThreadGroup threadGroup) throws LaunchException { if (!file.isApplet()) { throw launchError(new LaunchException(file, null, FATAL, "Application Error", "Not an applet file.", "An attempt was made to load a non-applet file as an applet.")); } @@ -480,7 +486,7 @@ private ApplicationInstance launchApplet(final JNLPFile file, final Container co AppletInstance applet = null; try { ServiceUtil.checkExistingSingleInstance(file); - applet = createApplet(file, cont); + applet = createApplet(file, threadGroup); applet.initialize(); applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance return applet; @@ -491,7 +497,7 @@ private ApplicationInstance launchApplet(final JNLPFile file, final Container co throw launchError(lex); } catch (Exception ex) { throw launchError(new LaunchException(file, ex, FATAL, "Launch Error", "Could not launch JNLP file.", "The application has not been initialized, for more information execute javaws/browser from the command line and send a bug report.")); - }finally{ + } finally { if (handler != null) { handler.launchStarting(applet); } @@ -501,8 +507,9 @@ private ApplicationInstance launchApplet(final JNLPFile file, final Container co /** * Launches a JNLP installer. This method should be called from * a thread in the application's thread group. + * * @param file jnlp file to read installer from - * @return application + * @return application * @throws net.sourceforge.jnlp.LaunchException if deploy unrecoverably die */ private ApplicationInstance launchInstaller(final JNLPFile file) throws LaunchException { @@ -515,49 +522,38 @@ private ApplicationInstance launchInstaller(final JNLPFile file) throws LaunchEx * Create an AppletInstance. * * @param file the JNLP file - * @param cont container where to put applet * @return applet * @throws net.sourceforge.jnlp.LaunchException if deploy unrecoverably die */ - //FIXME - when multiple applets are on one page, this method is visited simultaneously + //FIXME - when multiple applets are on one page, this method is visited simultaneously //and then applets creates in little bit strange manner. This issue is visible with //randomly showing/notshowing splashscreens. //See also PluginAppletViewer.framePanel - private AppletInstance createApplet(final JNLPFile file, final Container cont) throws LaunchException { - try { - JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy, true); - - loader.enableCodeBase(); + private AppletInstance createApplet(final JNLPFile file, final ThreadGroup threadGroup) throws + LaunchException { + try { - ThreadGroup group = Thread.currentThread().getThreadGroup(); // appletInstance is needed by ServiceManager when looking up // services. This could potentially be done in applet constructor // so initialize appletInstance before creating applet. - final AppletInstance appletInstance; - if (cont == null) { - appletInstance = new AppletInstance(file, group, loader, null); - } else { - appletInstance = new AppletInstance(file, group, loader, null, cont); - } - - /* - * Due to PR2968, moved to earlier phase, so early stages of applet - * can access Thread.currentThread().getContextClassLoader(). - * - * However it is notable, that init and start still do not have access to right classloader. - * See LoadResources test. - */ - setContextClassLoaderForAllThreads(appletInstance.getThreadGroup(), appletInstance.getClassLoader()); - - loader.setApplication(appletInstance); + final AppletInstance appletInstance = new AppletInstance(file, threadGroup); + + /* + * Due to PR2968, moved to earlier phase, so early stages of applet + * can access Thread.currentThread().getContextClassLoader(). + * + * However it is notable, that init and start still do not have access to right classloader. + * See LoadResources test. + */ + setContextClassLoaderForAllThreads(appletInstance.getThreadGroup(), appletInstance.getClassLoader()); // Initialize applet now that ServiceManager has access to its // appletInstance. String appletName = file.getApplet().getMainClass(); - Class appletClass = loader.loadClass(appletName); + Class appletClass = appletInstance.getClassLoader().loadClass(appletName); Applet applet = (Applet) appletClass.newInstance(); - applet.setStub((AppletStub)cont); + applet.setStub(null); // Finish setting up appletInstance. appletInstance.setApplet(applet); appletInstance.getAppletEnvironment().setApplet(applet); @@ -570,19 +566,15 @@ private AppletInstance createApplet(final JNLPFile file, final Container cont) t /** * Creates an Application. + * * @param file the JNLP file * @return application * @throws net.sourceforge.jnlp.LaunchException if deploy unrecoverably die */ - private ApplicationInstance createApplication(final JNLPFile file) throws LaunchException { + private ApplicationInstance createApplication(final JNLPFile file, final ThreadGroup threadGroup) throws + LaunchException { try { - JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy, false); - ThreadGroup group = Thread.currentThread().getThreadGroup(); - - ApplicationInstance app = new ApplicationInstance(file, group, loader); - loader.setApplication(app); - - return app; + return new ApplicationInstance(file, threadGroup); } catch (Exception ex) { throw new LaunchException(file, ex, FATAL, "Initialization Error", "Could not initialize application.", "The application has not been initialized, for more information execute javaws from the command line."); } @@ -590,10 +582,11 @@ private ApplicationInstance createApplication(final JNLPFile file) throws Launch /** * Create a thread group for the JNLP file. + * * @param file the JNLP file - * @return ThreadGroup for this app/applet + * @return ThreadGroup for this app/applet */ - private ThreadGroup createThreadGroup(final JNLPFile file) { + private static ThreadGroup createThreadGroup(final JNLPFile file) { return new ThreadGroup(mainGroup, file.getTitle()); } @@ -611,7 +604,7 @@ private LaunchException launchError(LaunchException ex) { /** * Do hacks on per-application level to allow different AppContexts to work - * + *

    * see JNLPRuntime#doMainAppContextHacks */ private static void doPerApplicationAppContextHacks() { @@ -626,63 +619,4 @@ private static void doPerApplicationAppContextHacks() { new ParserDelegator(); } - /** - * This runnable is used to call the appropriate launch method - * for the application, applet, or installer in its thread group. - */ - private class TgThread extends Thread { // ThreadGroupThread - private final JNLPFile file; - private ApplicationInstance application; - private LaunchException exception; - private final Container cont; - - TgThread(JNLPFile file, Container cont) { - super(createThreadGroup(file), file.getTitle()); - this.file = file; - this.cont = cont; - } - - @Override - public void run() { - try { - // Do not create new AppContext if we're using NetX and icedteaplugin. - // The plugin needs an AppContext too, but it has to be created earlier. - AppContextFactory.createNewAppContext(); - doPerApplicationAppContextHacks(); - - if (file.isApplication()) { - application = launchApplication(file); - } - else if (file.isApplet()) { - application = launchApplet(file, cont); - } - else if (file.isInstaller()) { - application = launchInstaller(file); - } - else { - throw launchError(new LaunchException(file, null, - FATAL, "Application Error", "Not a launchable JNLP file.", - "File must be a JNLP application, applet, or installer type.")); - } - } catch (LaunchException ex) { - LOG.error("Launch exception", ex); - exception = ex; - // Exit if we can't launch the application. - JNLPRuntime.exit(1); - } catch (Throwable ex) { - LOG.error("General Throwable encountered:", ex); - throw new RuntimeException(ex); - } - } - - LaunchException getException() { - return exception; - } - - ApplicationInstance getApplication() { - return application; - } - - } - } diff --git a/core/src/main/java/net/sourceforge/jnlp/NullJnlpFileException.java b/core/src/main/java/net/sourceforge/jnlp/NullJnlpFileException.java deleted file mode 100644 index e1c01e735..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/NullJnlpFileException.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.sourceforge.jnlp; - -/* -Copyright (C) 2012 Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -public class NullJnlpFileException extends NullPointerException { - - public NullJnlpFileException() { - super(); - } - - public NullJnlpFileException(String s) { - super(s); - } - -} diff --git a/core/src/main/java/net/sourceforge/jnlp/Parser.java b/core/src/main/java/net/sourceforge/jnlp/Parser.java index ed9e6a5cb..8d6b9b6b6 100644 --- a/core/src/main/java/net/sourceforge/jnlp/Parser.java +++ b/core/src/main/java/net/sourceforge/jnlp/Parser.java @@ -37,12 +37,13 @@ import net.adoptopenjdk.icedteaweb.jnlp.element.information.RelatedContentDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.information.ShortcutDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDownloadDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JREDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PackageDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PropertyDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.update.UpdateCheck; import net.adoptopenjdk.icedteaweb.jnlp.element.update.UpdateDesc; @@ -94,7 +95,6 @@ import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.DownloadStrategy.EAGER; import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.DownloadStrategy.LAZY; import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc.EXT_DOWNLOAD_ELEMENT; -import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc.EXT_PART_ATTRIBUTE; import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc.ARCH_ATTRIBUTE; import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc.EXTENSION_ELEMENT; import static net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc.J2SE_ELEMENT; @@ -129,7 +129,7 @@ public final class Parser { private static final Logger LOG = LoggerFactory.getLogger(Parser.class); - private static String MAINCLASS = "main-class"; + private static final String MAINCLASS = "main-class"; private static final Pattern anyWhiteSpace = Pattern.compile("\\s"); // defines netx.jnlp.Node class if using Tiny XML or Nano XML @@ -553,7 +553,7 @@ private JARDesc getJAR(final XmlNode node) throws ParseException { } } - return new JARDesc(location, versionString, part, lazy, main, nativeJar, true); + return new JARDesc(location, versionString, part, lazy, main, nativeJar); } @@ -570,15 +570,20 @@ private ExtensionDesc getExtension(final XmlNode node) throws ParseException { final ExtensionDesc ext = new ExtensionDesc(name, version, location); - final XmlNode dload[] = getChildNodes(node, EXT_DOWNLOAD_ELEMENT); - for (XmlNode dload1 : dload) { - final boolean lazy = LAZY.getValue().equals(getAttribute(dload1, ExtensionDesc.DOWNLOAD_ATTRIBUTE, EAGER.getValue())); - ext.addPart(getRequiredAttribute(dload1, EXT_PART_ATTRIBUTE, null, strict), getAttribute(dload1, ExtensionDesc.PART_ATTRIBUTE, null), lazy); + for (XmlNode downloadNode : getChildNodes(node, EXT_DOWNLOAD_ELEMENT)) { + ext.addDownload(getExtensionDownload(downloadNode)); } return ext; } + private ExtensionDownloadDesc getExtensionDownload(XmlNode node) throws ParseException { + final boolean lazy = LAZY.getValue().equals(getAttribute(node, ExtensionDownloadDesc.DOWNLOAD_ATTRIBUTE, EAGER.getValue())); + final String extPart = getRequiredAttribute(node, ExtensionDownloadDesc.EXT_PART_ATTRIBUTE, null, strict); + final String part = getAttribute(node, ExtensionDownloadDesc.PART_ATTRIBUTE, null); + return new ExtensionDownloadDesc(extPart, part, lazy); + } + /** * @return the Property element at the specified node. * @@ -789,27 +794,18 @@ public SecurityDesc getSecurity(final XmlNode parent) throws ParseException { } } - Object type = SecurityDesc.SANDBOX_PERMISSIONS; - ApplicationPermissionLevel applicationPermissionLevel = ApplicationPermissionLevel.NONE; - - if (nodes.length == 0) { - type = SecurityDesc.SANDBOX_PERMISSIONS; - applicationPermissionLevel = ApplicationPermissionLevel.NONE; - } else if (null != getChildNode(nodes[0], ApplicationPermissionLevel.ALL.getValue())) { - type = SecurityDesc.ALL_PERMISSIONS; - applicationPermissionLevel = ApplicationPermissionLevel.ALL; - } else if (null != getChildNode(nodes[0], ApplicationPermissionLevel.J2EE.getValue())) { - type = SecurityDesc.J2EE_PERMISSIONS; - applicationPermissionLevel = ApplicationPermissionLevel.J2EE; - } else if (strict) { - throw new ParseException("security element specified but does not contain a permissions element."); - } + ApplicationEnvironment applicationEnvironment = ApplicationEnvironment.SANDBOX; - if (base != null) { - return new SecurityDesc(file, applicationPermissionLevel, type, base); - } else { - return new SecurityDesc(file, applicationPermissionLevel, type, null); + if (nodes.length == 1) { + if (null != getChildNode(nodes[0], ApplicationEnvironment.ALL.getValue())) { + applicationEnvironment = ApplicationEnvironment.ALL; + } else if (null != getChildNode(nodes[0], ApplicationEnvironment.J2EE.getValue())) { + applicationEnvironment = ApplicationEnvironment.J2EE; + } else if (strict) { + throw new ParseException("security element specified but does not contain a permissions element."); + } } + return new SecurityDesc(applicationEnvironment); } /** @@ -819,8 +815,8 @@ private boolean isTrustedEnvironment() { final XmlNode security = getChildNode(root, SECURITY_ELEMENT); if (security != null) { - if (getChildNode(security, ApplicationPermissionLevel.ALL.getValue()) != null - || getChildNode(security, ApplicationPermissionLevel.J2EE.getValue()) != null) { + if (getChildNode(security, ApplicationEnvironment.ALL.getValue()) != null + || getChildNode(security, ApplicationEnvironment.J2EE.getValue()) != null) { return true; } } diff --git a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java index 08de9aa7e..8951dbf00 100644 --- a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java +++ b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java @@ -17,24 +17,17 @@ package net.sourceforge.jnlp.cache; import net.adoptopenjdk.icedteaweb.StringUtils; -import net.adoptopenjdk.icedteaweb.client.parts.downloadindicator.DownloadIndicator; -import net.adoptopenjdk.icedteaweb.client.parts.downloadindicator.DummyDownloadIndicator; import net.adoptopenjdk.icedteaweb.io.FileUtils; -import net.adoptopenjdk.icedteaweb.jnlp.element.EntryPoint; -import net.adoptopenjdk.icedteaweb.jnlp.element.application.AppletDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.application.ApplicationDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.extension.InstallerDesc; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTracker; import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; import net.adoptopenjdk.icedteaweb.resources.cache.Cache; import net.adoptopenjdk.icedteaweb.resources.cache.CacheFile; import net.adoptopenjdk.icedteaweb.resources.cache.CacheId; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; -import javax.jnlp.DownloadServiceListener; import java.io.File; import java.net.URISyntaxException; import java.net.URL; @@ -45,7 +38,6 @@ import java.util.Optional; import java.util.function.Consumer; -import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; import static net.sourceforge.jnlp.util.UrlUtils.JAR_PROTOCOL; @@ -75,7 +67,7 @@ public class CacheUtil { */ public static File downloadAndGetCacheFile(final URL location, final VersionString version) { try { - final ResourceTracker rt = new ResourceTracker(); + final ResourceTracker rt = new DefaultResourceTracker(); rt.addResource(location, version); return rt.getCacheFile(location); } catch (Exception ex) { @@ -224,58 +216,5 @@ public static String hex(String origName, String candidate) throws NoSuchAlgorit return hexString.toString(); } - /** - * Waits until the resources are downloaded, while showing a - * progress indicator. - * - * @param jnlpClassLoader the classloader - * @param tracker the resource tracker - * @param resources the resources to wait for - * @param title name of the download - */ - public static void waitForResources(final JNLPClassLoader jnlpClassLoader, final ResourceTracker tracker, final URL[] resources, final String title) { - try { - final DownloadIndicator indicator = Optional.ofNullable(JNLPRuntime.getDefaultDownloadIndicator()) - .orElseGet(() -> new DummyDownloadIndicator()); - final DownloadServiceListener listener = getDownloadServiceListener(jnlpClassLoader, title, resources, indicator); - try { - for (URL url : resources) { - tracker.addDownloadListener(url, resources, listener); - } - tracker.waitForResources(resources); - } finally { - indicator.disposeListener(listener); - } - } catch (Exception ex) { - LOG.error("Downloading of resources ended with error", ex); - } - } - - private static DownloadServiceListener getDownloadServiceListener(final JNLPClassLoader jnlpClassLoader, final String title, final URL[] undownloaded, final DownloadIndicator indicator) { - final EntryPoint entryPoint = jnlpClassLoader.getJNLPFile().getEntryPointDesc(); - String progressClass = null; - if (entryPoint instanceof ApplicationDesc) { - final ApplicationDesc applicationDesc = (ApplicationDesc) entryPoint; - progressClass = applicationDesc.getProgressClass(); - } else if (entryPoint instanceof AppletDesc) { - final AppletDesc appletDesc = (AppletDesc) entryPoint; - progressClass = appletDesc.getProgressClass(); - } else if (entryPoint instanceof InstallerDesc) { - final InstallerDesc installerDesc = (InstallerDesc) entryPoint; - progressClass = installerDesc.getProgressClass(); - } - - if (progressClass != null) { - try { - final Class downloadProgressIndicatorClass = jnlpClassLoader.loadClass(progressClass); - return (DownloadServiceListener) downloadProgressIndicatorClass.newInstance(); - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) { - LOG.warn(format("Could not load progress class '%s' specified in JNLP file, " + - "use default download progress indicator instead.", progressClass), ex); - } - } - - return indicator.getListener(title, undownloaded); - } } diff --git a/core/src/main/java/net/sourceforge/jnlp/config/Defaults.java b/core/src/main/java/net/sourceforge/jnlp/config/Defaults.java index 15e739e80..51da6db70 100644 --- a/core/src/main/java/net/sourceforge/jnlp/config/Defaults.java +++ b/core/src/main/java/net/sourceforge/jnlp/config/Defaults.java @@ -36,9 +36,9 @@ import net.adoptopenjdk.icedteaweb.config.ValidatorFactory; -import net.adoptopenjdk.icedteaweb.config.validators.SecurityValueValidator; import net.adoptopenjdk.icedteaweb.jnlp.element.information.ShortcutDesc; import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; +import net.adoptopenjdk.icedteaweb.security.SecurityLevel; import net.sourceforge.jnlp.proxy.ProxyType; import java.util.Arrays; @@ -465,8 +465,8 @@ public class Defaults { */ Setting.createDefault( ConfigurationConstants.KEY_SECURITY_LEVEL, - null, - new SecurityValueValidator() + SecurityLevel.HIGH.name(), + ValidatorFactory.createStringValidator(SecurityLevel.values()) ), Setting.createDefault( diff --git a/core/src/main/java/net/sourceforge/jnlp/config/PathsAndFiles.java b/core/src/main/java/net/sourceforge/jnlp/config/PathsAndFiles.java index 070c2992a..64312dd5a 100644 --- a/core/src/main/java/net/sourceforge/jnlp/config/PathsAndFiles.java +++ b/core/src/main/java/net/sourceforge/jnlp/config/PathsAndFiles.java @@ -112,6 +112,7 @@ public String getPropertiesKey() { //javaws is saving here, itweb-settings may modify them public static final InfrastructureFileDescriptor MENUS_DIR = new MenuFileDescriptor(Target.JAVAWS, Target.ITWEB_SETTINGS); public static final InfrastructureFileDescriptor APPLET_TRUST_SETTINGS_USER = new ItwConfigFileDescriptor(ConfigurationConstants.APPLET_TRUST_SETTINGS, "FILEextasuser", Target.JAVAWS, Target.ITWEB_SETTINGS); + public static final InfrastructureFileDescriptor USER_DECISIONS_FILE_STORE = new ItwConfigFileDescriptor(".userDecisions.json", "UserDecisions", Target.JAVAWS, Target.ITWEB_SETTINGS); public static final InfrastructureFileDescriptor APPLET_TRUST_SETTINGS_SYS = new SystemDeploymentConfigFileDescriptor(ConfigurationConstants.APPLET_TRUST_SETTINGS, "FILEextasadmin", Target.JAVAWS, Target.ITWEB_SETTINGS); public static final InfrastructureFileDescriptor ITW_SYSTEM_DEPLOYMENT_CFG = new SystemDeploymentConfigFileDescriptor(ConfigurationConstants.ITW_DEPLOYMENT_CONFIG_FILE, "FILEglobaldp", Target.JAVAWS, Target.ITWEB_SETTINGS); public static final InfrastructureFileDescriptor ETC_DEPLOYMENT_CFG = new SystemDeploymentConfigFileDescriptor(ConfigurationConstants.DEPLOYMENT_CONFIG_FILE, "FILEglobaldp", Target.JAVAWS, Target.ITWEB_SETTINGS); diff --git a/core/src/main/java/net/sourceforge/jnlp/proxy/JNLPProxySelector.java b/core/src/main/java/net/sourceforge/jnlp/proxy/JNLPProxySelector.java index 8cad6b597..88482e11d 100644 --- a/core/src/main/java/net/sourceforge/jnlp/proxy/JNLPProxySelector.java +++ b/core/src/main/java/net/sourceforge/jnlp/proxy/JNLPProxySelector.java @@ -111,10 +111,10 @@ public JNLPProxySelector(DeploymentConfiguration config) { .filter(s -> !StringUtils.isBlank(s)) .collect(Collectors.toList()); - bypassLocal = Boolean.valueOf(config + bypassLocal = Boolean.parseBoolean(config .getProperty(ConfigurationConstants.KEY_PROXY_BYPASS_LOCAL)); - sameProxy = Boolean.valueOf(config.getProperty(ConfigurationConstants.KEY_PROXY_SAME)); + sameProxy = Boolean.parseBoolean(config.getProperty(ConfigurationConstants.KEY_PROXY_SAME)); proxyHttpHost = getHost(config, ConfigurationConstants.KEY_PROXY_HTTP_HOST); proxyHttpPort = getPort(config, ConfigurationConstants.KEY_PROXY_HTTP_PORT); diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/AppContextFactory.java b/core/src/main/java/net/sourceforge/jnlp/runtime/AppContextFactory.java index c1d340397..86da8c4ab 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/AppContextFactory.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/AppContextFactory.java @@ -1,6 +1,5 @@ package net.sourceforge.jnlp.runtime; -import net.sourceforge.jnlp.runtime.classloader.DelegatingClassLoader; import sun.awt.SunToolkit; public class AppContextFactory { @@ -9,12 +8,8 @@ public static void createNewAppContext() { //already a call to AppContext.getAppContext(...) initializes the EventQueue.class ClassLoader originalLoader = Thread.currentThread().getContextClassLoader(); try { - DelegatingClassLoader delegatingLoader = DelegatingClassLoader.getInstance(); - delegatingLoader.setClassLoader(originalLoader); - - Thread.currentThread().setContextClassLoader(delegatingLoader); SunToolkit.createNewAppContext(); - }finally { + } finally { //restore original classloader Thread.currentThread().setContextClassLoader(originalLoader); } diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/AppletInstance.java b/core/src/main/java/net/sourceforge/jnlp/runtime/AppletInstance.java index dc4a3b903..c09174315 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/AppletInstance.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/AppletInstance.java @@ -16,15 +16,16 @@ package net.sourceforge.jnlp.runtime; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.util.WeakList; import java.applet.Applet; -import java.awt.Container; -import java.awt.Frame; +import java.awt.Window; +import java.util.Optional; /** * Represents a launched application instance created from a JNLP @@ -36,7 +37,7 @@ */ public class AppletInstance extends ApplicationInstance { - private final static Logger LOG = LoggerFactory.getLogger(AppletInstance.class); + private static final Logger LOG = LoggerFactory.getLogger(AppletInstance.class); /** whether the applet's stop and destroy methods have been called */ private boolean appletStopped = false; @@ -45,20 +46,20 @@ public class AppletInstance extends ApplicationInstance { private Applet applet; /** the applet environment */ - final private AppletEnvironment environment; + private final AppletEnvironment environment; + + /** + * weak list of windows opened by the application + */ + private final WeakList weakWindows = new WeakList<>(); /** * Create a New Task based on the Specified URL * @param file pluginbridge to build instance on - * @param group thread group of this instance - * @param loader classloader for this instance - * @param applet applet of this instance + * */ - public AppletInstance(JNLPFile file, ThreadGroup group, JNLPClassLoader loader, Applet applet) { - super(file, group, loader); - - this.applet = applet; - + public AppletInstance(JNLPFile file, final ThreadGroup threadGroup) throws LaunchException { + super(file, threadGroup); this.environment = new AppletEnvironment(file, this); } @@ -74,50 +75,6 @@ public void setApplet(Applet applet) { this.applet = applet; } - /** - * Create a New Task based on the Specified URL - * @param file pluginbridge to build instance on - * @param group thread group of this instance - * @param loader classloader for this instance - * @param applet applet of this instance - * @param cont Container where to place applet - */ - public AppletInstance(JNLPFile file, ThreadGroup group, JNLPClassLoader loader, Applet applet, Container cont) { - super(file, group, loader); - this.applet = applet; - this.environment = new AppletEnvironment(file, this, cont); - } - - /** - * Sets whether the applet is resizable or not. Applets default - * to being not resizable. - * @param resizable boolean to allow resizing - */ - public void setResizable(boolean resizable) { - Container c = environment.getAppletFrame(); - if (c instanceof Frame) - ((Frame) c).setResizable(resizable); - } - - /** - * @return whether the applet is resizable. - */ - public boolean isResizable() { - Container c = environment.getAppletFrame(); - if (c instanceof Frame) - return ((Frame) c).isResizable(); - - return false; - } - - /** - * @return the application title. - */ - @Override - public String getTitle() { - return getJNLPFile().getApplet().getName(); - } - /** * @return the applet environment. */ @@ -132,11 +89,16 @@ public Applet getApplet() { return applet; } + @Override + protected String determineMainClass(JNLPFile file, ResourceTracker tracker) { + return null; + } + /** * Stop the application and destroy its resources. */ @Override - public void destroy() { + public synchronized void destroy() { if (appletStopped) return; @@ -146,12 +108,26 @@ public void destroy() { applet.stop(); applet.destroy(); } catch (Exception ex) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, ex); + LOG.error("Exception while destroying AppletInstance", ex); } environment.destroy(); + weakWindows.forEach(w -> Optional.ofNullable(w).ifPresent(win -> win.dispose())); + weakWindows.clear(); + super.destroy(); } + /** + * Adds a window that this application opened. When the + * application is disposed, these windows will also be disposed. + * + * @param window to be added + */ + void addWindow(Window window) { + weakWindows.add(window); + weakWindows.trimToSize(); + } + } diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationExecutor.java b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationExecutor.java new file mode 100644 index 000000000..9035a63cf --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationExecutor.java @@ -0,0 +1,46 @@ +package net.sourceforge.jnlp.runtime; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + +/** + * Runs the JNLP application in a child thread group of the {@code mainGroup}. + */ +public class ApplicationExecutor { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationExecutor.class); + + private static final ThreadGroup mainGroup = new ThreadGroup("Application-Threads-Group"); + + public CompletableFuture execute(final String applicationTitle, final Supplier launchApplication) { + Assert.requireNonNull(launchApplication, "launchApplication"); + + final ThreadFactory applicationThreadFactory = createThreadFactory(applicationTitle); + final ExecutorService applicationExecutor = Executors.newCachedThreadPool(applicationThreadFactory); + return CompletableFuture.supplyAsync(launchApplication, applicationExecutor); + + } + + private ThreadFactory createThreadFactory(final String applicationTitle) { + return new ThreadFactory() { + + private final AtomicLong counter = new AtomicLong(0); + + private final ThreadGroup group = new ThreadGroup(mainGroup, applicationTitle); + + public Thread newThread(Runnable r) { + final Thread thread = new Thread(group, r, "Application-" + applicationTitle + "-thread-" + counter.incrementAndGet()); + thread.setUncaughtExceptionHandler((t, e) -> LOG.error("Error in application thread for app '" + applicationTitle + "'", e)); + return thread; + } + }; + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationInstance.java b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationInstance.java index f9f22a4a4..7516e1c7c 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationInstance.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationInstance.java @@ -16,24 +16,38 @@ package net.sourceforge.jnlp.runtime; +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartExtractor; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PropertyDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTrackerFactory; +import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; +import net.adoptopenjdk.icedteaweb.resources.ResourceTrackerFactory; +import net.adoptopenjdk.icedteaweb.security.PermissionsManager; import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import net.sourceforge.jnlp.LaunchException; import net.sourceforge.jnlp.config.DeploymentConfiguration; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; -import net.sourceforge.jnlp.util.WeakList; +import net.sourceforge.jnlp.services.PartsCache; +import net.sourceforge.jnlp.util.JarFile; import sun.awt.AppContext; -import javax.swing.event.EventListenerList; -import java.awt.Window; +import java.io.File; import java.io.IOException; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; +import java.security.PermissionCollection; import java.security.PrivilegedAction; import java.security.ProtectionDomain; +import java.util.jar.Attributes; +import java.util.stream.Stream; + +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.ALL; +import static net.sourceforge.jnlp.LaunchException.FATAL; /** * Represents a running instance of an application described in a @@ -53,65 +67,72 @@ public class ApplicationInstance { // todo: should attempt to unload the environment variables // installed by the application. - /** the file */ private final JNLPFile file; - - /** the thread group */ + private final String mainClass; private final ThreadGroup group; + private final PartsHandler partsHandler; + private final JnlpApplicationClassLoader loader; + private final ApplicationPermissions applicationPermissions; - /** the classloader */ - private final JNLPClassLoader loader; - - /** whether the application has stopped running */ + /** + * whether the application has stopped running + */ private boolean stopped = false; - /** weak list of windows opened by the application */ - private final WeakList weakWindows = new WeakList<>(); - - /** list of application listeners */ - private final EventListenerList listeners = new EventListenerList(); + private ApplicationEnvironment applicationEnvironment; - /** whether or not this application is signed */ - private boolean isSigned; /** * Create an application instance for the file. This should be done in the * appropriate {@link ThreadGroup} only. + * * @param file jnlpfile for which the instance do exists - * @param group thread group to which it belongs - * @param loader loader for this application */ - public ApplicationInstance(JNLPFile file, ThreadGroup group, JNLPClassLoader loader) { - this.file = file; - this.group = group; - this.loader = loader; - this.isSigned = loader.getSigning(); - AppContext.getAppContext(); + public ApplicationInstance(final JNLPFile file, final ThreadGroup applicationThreadGroup) { + this(file, new DefaultResourceTrackerFactory(), applicationThreadGroup); } /** - * Notify listeners that the application has been terminated. + * Visible for testing. For productive code please use {@link #ApplicationInstance(JNLPFile, ThreadGroup)} (JNLPFile)}. */ - private void fireDestroyed() { - Object[] list = listeners.getListenerList(); - ApplicationEvent event = null; + public ApplicationInstance(final JNLPFile file, ResourceTrackerFactory trackerFactory, final ThreadGroup applicationThreadGroup) { + this.file = file; + this.applicationEnvironment = file.getSecurity().getApplicationEnvironment(); + this.group = applicationThreadGroup; + final ResourceTracker tracker = trackerFactory.create(true, file.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy()); + this.applicationPermissions = new ApplicationPermissions(tracker); + final JNLPFileFactory fileFactory = new JNLPFileFactory(); + final PartExtractor extractor = new PartExtractor(file, fileFactory); - for (int i = list.length - 1; i > 0; i -= 2) { // last to first required - if (event == null) - event = new ApplicationEvent(this); + this.partsHandler = new PartsHandler(extractor.getParts(), file, tracker); + this.loader = new JnlpApplicationClassLoader(partsHandler); - ((ApplicationListener) list[i]).applicationDestroyed(event); - } + this.mainClass = determineMainClass(file, tracker); } /** * Initialize the application's environment (installs * environment variables, etc). */ - public void initialize() { + public void initialize() throws LaunchException { + + ApplicationManager.addApplication(this); + + loader.initializeEagerJars(); + + AppContext.getAppContext(); + + installSandboxIfRequested(); + installEnvironment(); - final DeploymentConfiguration configuration = JNLPRuntime.getConfiguration(); - JNLPRuntime.getExtensionPoint().createMenuAndDesktopIntegration(configuration).addMenuAndDesktopEntries(file); + installShortcutsIfRequested(); + } + + private void installSandboxIfRequested() throws LaunchException { + if (JNLPRuntime.isSecurityEnabled() && applicationEnvironment != ALL) { + throw new LaunchException(file, null, FATAL, "Not Supported", "Sandbox not supported", + "Currently Icedtea-Web does not support sandboxing of a managed application"); + } } /** @@ -135,10 +156,8 @@ private void installEnvironment() { if (!(props.length == 0)) { final CodeSource cs = new CodeSource(null, (java.security.cert.Certificate[]) null); - final JNLPClassLoader loader = this.loader; - final SecurityDesc s = loader.getSecurity(); - final ProtectionDomain pd = new ProtectionDomain(cs, s.getPermissions(cs), null, null); - final AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] { pd }); + final ProtectionDomain pd = new ProtectionDomain(cs, PermissionsManager.getPermissions(file, cs, applicationEnvironment), null, null); + final AccessControlContext acc = new AccessControlContext(new ProtectionDomain[]{pd}); final PrivilegedAction setPropertiesAction = () -> { for (PropertyDesc propDesc : props) { @@ -156,68 +175,57 @@ private void installEnvironment() { System.setProperty(DEPLOYMENT_SYSPROP, DEPLOYMENT_SYSPROP_VALUE); } + private void installShortcutsIfRequested() { + final DeploymentConfiguration configuration = JNLPRuntime.getConfiguration(); + JNLPRuntime.getExtensionPoint().createMenuAndDesktopIntegration(configuration).addMenuAndDesktopEntries(file); + } + /** * Returns the jnlpfile on which is this application based + * * @return JNLP file for this task. */ public JNLPFile getJNLPFile() { return file; } - /** - * Returns the application title. - * @return the title of this application - */ - public String getTitle() { - return file.getTitle(); + public PartsCache getPartsCache() { + return partsHandler; } /** - * Returns whether the application is running. - * @return state of application + * @return the environment for the application to run in. */ - public boolean isRunning() { - return !stopped; + public ApplicationEnvironment getApplicationEnvironment() { + return applicationEnvironment; + } + + public void setApplicationEnvironment(ApplicationEnvironment applicationEnvironment) { + this.applicationEnvironment = applicationEnvironment; } /** * Stop the application and destroy its resources. */ - @SuppressWarnings("deprecation") - public void destroy() { + public synchronized void destroy() { if (stopped) return; try { - // destroy resources - for (Window w : weakWindows) { - if (w != null) - w.dispose(); - } - - weakWindows.clear(); - - // interrupt threads Thread[] threads = new Thread[group.activeCount() * 2]; - int nthreads = group.enumerate(threads); - for (int i = 0; i < nthreads; i++) { - LOG.info("Interrupt thread: {}", threads[i]); - threads[i].interrupt(); - } - - // then stop - Thread.yield(); - nthreads = group.enumerate(threads); - for (int i = 0; i < nthreads; i++) { - LOG.info("Stop thread: {}", threads[i]); - threads[i].stop(); - } - - // then destroy - except Thread.destroy() not implemented in jdk + group.enumerate(threads); + Stream.of(threads).forEach(t -> { + try { + t.interrupt(); + } catch (final Exception e) { + LOG.error("Unable to interrupt application Thread '" + t.getName() + "'", e); + } + }); + } catch (final Exception e) { + LOG.error("ERROR IN DESTROYING APP!", e); } finally { stopped = true; - fireDestroyed(); } } @@ -228,8 +236,9 @@ public void destroy() { * @throws IllegalStateException if the app is not running */ public ThreadGroup getThreadGroup() throws IllegalStateException { - if (stopped) + if (stopped) { throw new IllegalStateException(); + } return group; } @@ -240,40 +249,41 @@ public ThreadGroup getThreadGroup() throws IllegalStateException { * @return the classloader of this application, unless it is stopped * @throws IllegalStateException if the app is not running */ - public JNLPClassLoader getClassLoader() throws IllegalStateException { - if (stopped) + public ClassLoader getClassLoader() throws IllegalStateException { + if (stopped) { throw new IllegalStateException(); + } return loader; } - public String getMainClassName() throws IOException { - String mainName = file.getApplication().getMainClass(); + + public PermissionCollection getPermissions(CodeSource cs) { + return applicationPermissions.getPermissions(file, cs); + } + + public String getMainClassName() { + return mainClass; + } + + protected String determineMainClass(JNLPFile file, ResourceTracker tracker) { + final String mainName = file.getApplication().getMainClass(); // When the application-desc field is empty, we should take a // look at the main jar for the main class. - if (mainName == null) { - mainName = getClassLoader().getMainClassNameFromManifest(file.getResources().getMainJAR()); + if (mainName != null) { + return mainName; } - return mainName; - } - - /** - * Adds a window that this application opened. When the - * application is disposed, these windows will also be disposed. - * @param window to be added - */ - void addWindow(Window window) { - weakWindows.add(window); - weakWindows.trimToSize(); - } + final File f = tracker.getCacheFile(file.getResources().getMainJAR().getLocation()); + if (f != null) { + try (final JarFile mainJar = new JarFile(f)) { + return mainJar.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + } catch (IOException e) { + throw new RuntimeException(e); + } + } - /** - * @return whether or not this application is signed. - */ - public boolean isSigned() { - return isSigned; + return null; } - } diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationManager.java new file mode 100644 index 000000000..1130f8b19 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationManager.java @@ -0,0 +1,55 @@ +package net.sourceforge.jnlp.runtime; + +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.JNLPFile; + +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.Optional; +import java.util.WeakHashMap; + +import static java.security.AccessController.doPrivileged; +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static java.util.Optional.ofNullable; + +public class ApplicationManager { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationManager.class); + + private static final WeakHashMap applicationHolder = new WeakHashMap<>(); + + public static Optional getApplication() { + return getApplication(Thread.currentThread().getContextClassLoader()); + } + + public static Optional getApplication(final ClassLoader classLoader) { + final ApplicationInstance instance = applicationHolder.get(classLoader); + if (instance != null) { + return of(instance); + } + + return getParentOf(classLoader).flatMap(ApplicationManager::getApplication); + } + + private static Optional getParentOf(ClassLoader classLoader) { + try { + return ofNullable(doPrivileged((PrivilegedAction) classLoader::getParent)); + } + catch (Exception e) { + LOG.warn("Exception while getting parent class loader", e); + return empty(); + } + } + + public static Optional getApplication(final JNLPFile file) { + return applicationHolder.values().stream() + .filter(instance -> Objects.equals(instance.getJNLPFile(), file)) + .findFirst(); + } + + public static void addApplication(final ApplicationInstance applicationInstance) { + applicationHolder.put(applicationInstance.getClassLoader(), applicationInstance); + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationPermissions.java b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationPermissions.java new file mode 100644 index 000000000..da5cd7ffe --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/ApplicationPermissions.java @@ -0,0 +1,245 @@ +package net.sourceforge.jnlp.runtime; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.http.CloseableConnection; +import net.adoptopenjdk.icedteaweb.http.ConnectionFactory; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; +import net.adoptopenjdk.icedteaweb.security.PermissionsManager; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.util.UrlUtils; + +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.net.SocketPermission; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.adoptopenjdk.icedteaweb.security.PermissionsManager.getSandBoxPermissions; +import static sun.security.util.SecurityConstants.FILE_READ_ACTION; + +public class ApplicationPermissions { + private static final Logger LOG = LoggerFactory.getLogger(ApplicationPermissions.class); + + private final ResourceTracker tracker; + + /** + * Map of specific original (remote) CodeSource Urls to securitydesc + * Synchronized since this field may become shared data between multiple + * classloading threads. See loadClass(String) and + * CodebaseClassLoader.findClassNonRecursive(String). + */ + private final Map jarLocationSecurityMap = Collections.synchronizedMap(new HashMap<>()); + + /** + * the permissions for the cached jar files + */ + private final List resourcePermissions = new ArrayList<>(); + + /** + * Permissions granted by the user during runtime. + */ + private final List runtimePermissions = new ArrayList<>(); + + private final Set alreadyTried = Collections.synchronizedSet(new HashSet<>()); + + public ApplicationPermissions(final ResourceTracker tracker) { + this.tracker = tracker; + } + + public void addRuntimePermission(Permission p) { + runtimePermissions.add(p); + } + + /** + * Make permission objects for the classpath. + */ + public void addReadPermissionsForAllJars(final ResourcesDesc resources) { + + JARDesc[] jars = resources.getJARs(); + for (JARDesc jar : jars) { + Permission p = getReadPermission(jar.getLocation()); + + if (p == null) { + LOG.info("Unable to add permission for {}", jar.getLocation()); + } else { + resourcePermissions.add(p); + LOG.info("Permission added: {}", p.toString()); + } + } + } + + public void addReadPermissionForJar(final URL location) { + // Give read permissions to the cached jar file + AccessController.doPrivileged((PrivilegedAction) () -> { + Permission p = getReadPermission(location); + resourcePermissions.add(p); + return null; + }); + } + + public PermissionCollection getPermissions(final JNLPFile file, CodeSource codeSource) { + try { + Assert.requireNonNull(codeSource, "codeSource"); + + final Permissions result = new Permissions(); + + // should check for extensions or boot, automatically give all + // access w/o security dialog once we actually check certificates. + + // start with sandbox permissions as default + PermissionCollection permissions = getSandBoxPermissions(file); + + // if more than default is needed: + // 1. Code must be signed + // 2. ALL or J2EE permissions must be requested (note: plugin requests ALL automatically) + if (codeSource.getCodeSigners() != null) { + if (codeSource.getLocation() == null) { + throw new IllegalStateException("Code source location was null"); + } + final SecurityDesc codeSourceSecurity = getCodeSourceSecurity(codeSource.getLocation()); + if (codeSourceSecurity == null) { + throw new IllegalStateException("Code source security was null"); + } + final ApplicationEnvironment applicationEnvironment = codeSourceSecurity.getApplicationEnvironment(); + if (applicationEnvironment == null) { + LOG.error("Warning! Code source security application environment was null"); + } + if (applicationEnvironment == ApplicationEnvironment.ALL || applicationEnvironment == ApplicationEnvironment.J2EE) { + permissions = PermissionsManager.getPermissions(file, codeSource, applicationEnvironment); + } + } + + Collections.list(permissions.elements()).forEach(result::add); + resourcePermissions.forEach(result::add); // add in permission to read the cached JAR files + runtimePermissions.forEach(result::add); // add in the permissions that the user granted + + // Class from host X should be allowed to connect to host X + if (codeSource.getLocation() != null && codeSource.getLocation().getHost().length() > 0) { + result.add(new SocketPermission(UrlUtils.getHostAndPort(codeSource.getLocation()), + "connect, accept")); + } + + return result; + } catch (RuntimeException ex) { + LOG.error("Failed to get permissions", ex); + throw new RuntimeException("Failed to get permissions", ex); + } + } + + public void addSecurityForJarLocation(final URL location, SecurityDesc securityDesc) { + jarLocationSecurityMap.put(location, securityDesc); + } + + public SecurityDesc getSecurityForJarLocation(final URL location) { + return jarLocationSecurityMap.get(location); + } + + public Set getAllJarLocations() { + return jarLocationSecurityMap.keySet(); + } + + public AccessControlContext getAccessControlContextForClassLoading(final JNLPFile file, final List codeBaseLoaderUrls) { + AccessControlContext context = AccessController.getContext(); + + try { + context.checkPermission(new AllPermission()); + return context; // If context already has all permissions, don't bother + } catch (AccessControlException ace) { + // continue below + } + + // Since this is for class-loading, technically any class from one jar + // should be able to access a class from another, therefore making the + // original context code source irrelevant + PermissionCollection permissions = getSandBoxPermissions(file); + + // Local cache access permissions + for (Permission resourcePermission : resourcePermissions) { + permissions.add(resourcePermission); + } + + synchronized (this) { + getAllJarLocations().stream() + .map(l -> new SocketPermission(UrlUtils.getHostAndPort(l), "connect, accept")) + .forEach(permissions::add); + } + + // Permissions for codebase urls (if there is a loader) + codeBaseLoaderUrls.forEach(u -> permissions.add(new SocketPermission(UrlUtils.getHostAndPort(u), "connect, accept"))); + + ProtectionDomain pd = new ProtectionDomain(null, permissions); + + return new AccessControlContext(new ProtectionDomain[]{pd}); + } + + private SecurityDesc getCodeSourceSecurity(final URL source) { + final SecurityDesc storedValue = jarLocationSecurityMap.get(source); + if (storedValue == null) { + synchronized (alreadyTried) { + if (!alreadyTried.contains(source)) { + alreadyTried.add(source); + // FIXME: try to load the jar which is requesting the permissions, but was NOT downloaded by standard way + LOG.info("Application is trying to get permissions for {}, which was not added by standard way. Trying to download and verify!", source.toString()); + try { + final SecurityDesc newValue = jarLocationSecurityMap.get(source); + if (newValue != null) { + return newValue; + } + } catch (Throwable t) { + LOG.error("Error while getting security", t); + } + } + } + LOG.info("Error: No security instance for {}. The application may have trouble continuing", source.toString()); + return null; + } else { + return storedValue; + } + } + + private Permission getReadPermission(final URL location) { + if (CacheUtil.isCacheable(location)) { + final File cacheFile = tracker.getCacheFile(location); + if (cacheFile != null) { + return new FilePermission(cacheFile.getPath(), FILE_READ_ACTION); + } else { + LOG.debug("No cache file for cacheable resource '{}' found.", location); + return null; + } + } else { + // this is what URLClassLoader does + try (final CloseableConnection conn = ConnectionFactory.openConnection(location)) { + return conn.getPermission(); + } catch (IOException ioe) { + LOG.error("Exception while retrieving permissions from connection to " + location, ioe); + } + } + // should try to figure out the permission + return null; + } + +} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/DelegatingClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/DelegatingClassLoader.java new file mode 100644 index 000000000..e69de29bb diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/ItwMenuAndDesktopIntegration.java b/core/src/main/java/net/sourceforge/jnlp/runtime/ItwMenuAndDesktopIntegration.java index 39a3896f2..265e0c657 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/ItwMenuAndDesktopIntegration.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/ItwMenuAndDesktopIntegration.java @@ -1,13 +1,14 @@ package net.sourceforge.jnlp.runtime; import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.i18n.Translator; import net.adoptopenjdk.icedteaweb.jnlp.element.information.ShortcutDesc; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.os.OsUtil; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.config.ConfigurationConstants; import net.sourceforge.jnlp.security.AccessType; @@ -131,12 +132,12 @@ private AccessWarningPaneComplexReturn getComplexReturn(JNLPFile file, ShortcutD AccessWarningPaneComplexReturn r = new AccessWarningPaneComplexReturn(mainResult); if (mainResult){ if (sd.onDesktop()){ - r.setDesktop(new AccessWarningPaneComplexReturn.ShortcutResult(true)); + r.setDesktop(new ShortcutResult(true)); r.getDesktop().setBrowser(XDesktopEntry.getBrowserBin()); r.getDesktop().setShortcutType(AccessWarningPaneComplexReturn.Shortcut.BROWSER); } if (sd.getMenu() != null){ - r.setMenu(new AccessWarningPaneComplexReturn.ShortcutResult(true)); + r.setMenu(new ShortcutResult(true)); r.getMenu().setBrowser(XDesktopEntry.getBrowserBin()); r.getMenu().setShortcutType(AccessWarningPaneComplexReturn.Shortcut.BROWSER); } @@ -156,10 +157,10 @@ private AccessWarningPaneComplexReturn getComplexReturn(JNLPFile file, ShortcutD case ShortcutDesc.CREATE_ALWAYS: return new AccessWarningPaneComplexReturn(true); case ShortcutDesc.CREATE_ASK_USER: - return SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, file, null); + return Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, file, null); case ShortcutDesc.CREATE_ASK_USER_IF_HINTED: if (sd != null && (sd.onDesktop() || sd.toMenu())) { - return SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, file, null); + return Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, file, null); } case ShortcutDesc.CREATE_ALWAYS_IF_HINTED: if (sd != null && (sd.onDesktop() || sd.toMenu())) { diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPPolicy.java b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPPolicy.java index e937ebffd..1fe460035 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPPolicy.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPPolicy.java @@ -16,14 +16,13 @@ package net.sourceforge.jnlp.runtime; -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; import net.adoptopenjdk.icedteaweb.JavaSystemProperties; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.config.ConfigurationConstants; import net.sourceforge.jnlp.config.DeploymentConfiguration; import net.sourceforge.jnlp.config.PathsAndFiles; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; import java.io.File; import java.net.MalformedURLException; @@ -42,6 +41,7 @@ import java.security.ProtectionDomain; import java.security.URIParameter; import java.util.Enumeration; +import java.util.Optional; import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; @@ -76,22 +76,25 @@ public class JNLPPolicy extends Policy { private final URI jreExtDir; + private final JNLPSecurityManager securityManager; + /** * the system level policy for jnlps */ - private Policy systemJnlpPolicy = null; + private final Policy systemJnlpPolicy; /** * the user-level policy for jnlps */ - private Policy userJnlpPolicy = null; + private final Policy userJnlpPolicy; - protected JNLPPolicy() { + protected JNLPPolicy(final JNLPSecurityManager securityManager) { + this.securityManager = securityManager; shellSource = JNLPPolicy.class.getProtectionDomain().getCodeSource(); systemSource = Policy.class.getProtectionDomain().getCodeSource(); systemPolicy = Policy.getPolicy(); - systemJnlpPolicy = getPolicyFromConfig(ConfigurationConstants.KEY_SYSTEM_SECURITY_POLICY); + systemJnlpPolicy = getSystemSecurityPolicyFromConfig(); userJnlpPolicy = getPolicyFromUrl(PathsAndFiles.JAVA_POLICY.getFullPath()); String jre = JavaSystemProperties.getJavaHome(); @@ -99,8 +102,7 @@ protected JNLPPolicy() { } /** - * Return a mutable, heterogeneous-capable permission collection - * for the source. + * Return a mutable, heterogeneous-capable permission collection for the source. */ public PermissionCollection getPermissions(CodeSource source) { if (source.equals(systemSource) || source.equals(shellSource)) @@ -112,45 +114,42 @@ public PermissionCollection getPermissions(CodeSource source) { // if we check the SecurityDesc here then keep in mind that // code can add properties at runtime to the ResourcesDesc! - if (JNLPRuntime.getApplication() != null) { - if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { - JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); - - PermissionCollection clPermissions = cl.getPermissions(source); - - Enumeration e; - CodeSource appletCS = new CodeSource(JNLPRuntime.getApplication().getJNLPFile().getSourceLocation(), (java.security.cert.Certificate[]) null); + final Optional application = securityManager.getApplication(); + if (application.isPresent()) { + final PermissionCollection clPermissions = application.get().getPermissions(source); + + Enumeration e; + final JNLPFile jnlpFile = application.get().getJNLPFile(); + CodeSource appletCS = new CodeSource(jnlpFile.getSourceLocation(), (java.security.cert.Certificate[]) null); + + // systempolicy permissions need to be accounted for as well + e = systemPolicy.getPermissions(appletCS).elements(); + while (e.hasMoreElements()) { + clPermissions.add(e.nextElement()); + } - // systempolicy permissions need to be accounted for as well - e = systemPolicy.getPermissions(appletCS).elements(); + // and so do permissions from the jnlp-specific system policy + if (systemJnlpPolicy != null) { + e = systemJnlpPolicy.getPermissions(appletCS).elements(); while (e.hasMoreElements()) { clPermissions.add(e.nextElement()); } + } - // and so do permissions from the jnlp-specific system policy - if (systemJnlpPolicy != null) { - e = systemJnlpPolicy.getPermissions(appletCS).elements(); - while (e.hasMoreElements()) { - clPermissions.add(e.nextElement()); - } + // and permissions from jnlp-specific user policy too + if (userJnlpPolicy != null) { + e = userJnlpPolicy.getPermissions(appletCS).elements(); + while (e.hasMoreElements()) { + clPermissions.add(e.nextElement()); } - // and permissions from jnlp-specific user policy too - if (userJnlpPolicy != null) { - e = userJnlpPolicy.getPermissions(appletCS).elements(); - while (e.hasMoreElements()) { - clPermissions.add(e.nextElement()); - } - - CodeSource appletCodebaseSource = new CodeSource(JNLPRuntime.getApplication().getJNLPFile().getCodeBase(), (java.security.cert.Certificate[]) null); - e = userJnlpPolicy.getPermissions(appletCodebaseSource).elements(); - while (e.hasMoreElements()) { - clPermissions.add(e.nextElement()); - } + CodeSource appletCodebaseSource = new CodeSource(jnlpFile.getCodeBase(), (java.security.cert.Certificate[]) null); + e = userJnlpPolicy.getPermissions(appletCodebaseSource).elements(); + while (e.hasMoreElements()) { + clPermissions.add(e.nextElement()); } - - return clPermissions; } + return clPermissions; } // delegate to original Policy object; required to run under WebStart @@ -214,12 +213,11 @@ private boolean isSystemJar(final CodeSource source) { /** * Constructs a delegate policy based on a config setting * - * @param key a KEY_* in DeploymentConfiguration * @return a policy based on the configuration set by the user */ - private Policy getPolicyFromConfig(String key) { + private Policy getSystemSecurityPolicyFromConfig() { DeploymentConfiguration config = JNLPRuntime.getConfiguration(); - String policyLocation = config.getProperty(key); + String policyLocation = config.getProperty(ConfigurationConstants.KEY_SYSTEM_SECURITY_POLICY); return getPolicyFromUrl(policyLocation); } @@ -241,7 +239,7 @@ private Policy getPolicyFromUrl(String policyLocation) { } policy = getInstance("JavaPolicy", new URIParameter(policyUri)); } catch (IllegalArgumentException | NoSuchAlgorithmException | URISyntaxException | MalformedURLException e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); + LOG.error("Failed to get policy from url " + policyLocation, e); } } return policy; diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java index 87a93e45f..e683c3055 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java @@ -70,12 +70,12 @@ import java.nio.channels.FileLock; import java.security.AllPermission; import java.security.KeyStore; -import java.security.Policy; import java.security.Security; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.ServiceLoader; import java.util.stream.Collectors; @@ -103,7 +103,7 @@ */ public class JNLPRuntime { - private final static Logger LOG = LoggerFactory.getLogger(JNLPRuntime.class); + private static final Logger LOG = LoggerFactory.getLogger(JNLPRuntime.class); /** * java-abrt-connector can print out specific application String method, it is good to save visited urls for reproduce purposes. @@ -113,12 +113,6 @@ public class JNLPRuntime { */ private static String history = ""; - /** the security manager */ - private static JNLPSecurityManager security; - - /** the security policy */ - private static JNLPPolicy policy; - /** handles all security message to show appropriate security dialogs */ private static SecurityDialogMessageHandler securityDialogMessageHandler; @@ -152,9 +146,6 @@ public class JNLPRuntime { */ private static Boolean pluginDebug = null; - /** mutex to wait on, for initialization */ - public static Object initMutex = new Object(); - /** set to NEVER to indicate another JVM should not be spawned, even if necessary */ private static ForkingStrategy forkingStrategy = IF_JNLP_REQUIRES; @@ -165,7 +156,7 @@ public class JNLPRuntime { private static boolean trustNone = false; /** allows 301.302.303.307.308 redirects to be followed when downloading resources*/ - private static boolean allowRedirect = false;; + private static boolean allowRedirect = false; /** when this is true, ITW will not attempt any inet connections and will work only with what is in cache*/ private static boolean offlineForced = false; @@ -255,16 +246,8 @@ public static void initialize() throws IllegalStateException { ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start - policy = new JNLPPolicy(); - security = new JNLPSecurityManager(); // side effect: create JWindow - doMainAppContextHacks(); - if (securityEnabled && forkingStrategy.mayRunManagedApplication()) { - Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy - System.setSecurityManager(security); - } - securityDialogMessageHandler = startSecurityThreads(); // wire in custom authenticator for SSL connections @@ -298,18 +281,13 @@ public static void initialize() throws IllegalStateException { } - public static void reloadPolicy() { - policy.refresh(); - - } - /** * Returns a TrustManager ideal for the running VM. * * @return TrustManager the trust manager to use for verifying https certificates */ private static TrustManager getSSLSocketTrustManager() throws - ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { + IllegalAccessException, InstantiationException, InvocationTargetException { try { @@ -575,33 +553,12 @@ public static SecurityDialogMessageHandler getSecurityDialogHandler() { return securityDialogMessageHandler; } - /** - * Set a class that can exit the JVM; if not set then any class - * can exit the JVM. - * - * @param exitClass a class that can exit the JVM - * @throws IllegalStateException if caller is not the exit class - */ - public static void setExitClass(Class exitClass) { - checkExitClass(); - security.setExitClass(exitClass); - } - - /** - * Disables applets from calling exit. - * - * Once disabled, exit cannot be re-enabled for the duration of the JVM instance - */ - public static void disableExit() { - security.disableExit(); - } - /** * @return the current Application, or null if none can be * determined. */ - public static ApplicationInstance getApplication() { - return security.getApplication(); + public static Optional getApplication() { + return ApplicationManager.getApplication(); } /** @@ -624,7 +581,6 @@ public static boolean isSetDebug() { * @throws IllegalStateException if caller is not the exit class */ public static void setDebug(boolean enabled) { - checkExitClass(); debug = enabled; } @@ -636,7 +592,6 @@ public static void setDebug(boolean enabled) { * @throws IllegalStateException if caller is not the exit class */ public static void setDefaultUpdatePolicy(UpdatePolicy policy) { - checkExitClass(); updatePolicy = policy; } @@ -647,15 +602,6 @@ public static UpdatePolicy getDefaultUpdatePolicy() { return updatePolicy; } - /** - * Sets the default launch handler. - * @param handler default handler - */ - public static void setDefaultLaunchHandler(LaunchHandler handler) { - checkExitClass(); - JNLPRuntime.handler = handler; - } - /** * Returns the default launch handler. * @return default handler @@ -671,8 +617,6 @@ public static LaunchHandler getDefaultLaunchHandler() { * @throws IllegalStateException if caller is not the exit class */ public static void setDefaultDownloadIndicator(DownloadIndicator indicator) { - LOG.debug("Trying to set download indicator"); - checkExitClass(); LOG.debug("Setting download indicator to " + indicator); JNLPRuntime.indicator = indicator; } @@ -708,16 +652,6 @@ private static void checkInitialized() { throw new IllegalStateException("JNLPRuntime already initialized."); } - /** - * Throws an exception if called with security enabled but a caller is not - * the exit class and the runtime has been initialized. - */ - private static void checkExitClass() { - if (securityEnabled && initialized) - if (!security.isExitClass()) - throw new IllegalStateException("Caller is not the exit class"); - } - /** * Check whether the VM is in headless mode. */ @@ -908,20 +842,12 @@ public static void saveHistory(String documentBase) { } /** - * Used by java-abrt-connector via reflection - * @return history + * @param showWebSplash show splash screen at start of webstart application */ - private static String getHistory() { - return history; + public static void setShowWebSplash(boolean showWebSplash) { + JNLPRuntime.showWebSplash = showWebSplash; } - /** - * @param showWebSplash show splash screen at start of webstart application - */ - public static void setShowWebSplash(boolean showWebSplash) { - JNLPRuntime.showWebSplash = showWebSplash; - } - /** * @return show splash screen at start of webstart application */ diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java index e7ca39174..2b04d4933 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java @@ -19,17 +19,12 @@ import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils; -import net.sourceforge.jnlp.runtime.classloader.CodeBaseClassLoader; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; -import net.sourceforge.jnlp.security.AccessType; -import net.sourceforge.jnlp.services.ServiceUtil; import net.sourceforge.jnlp.util.WeakList; import sun.awt.AppContext; import java.awt.Window; -import java.net.SocketPermission; -import java.security.AccessControlException; import java.security.Permission; +import java.util.Optional; /** * Security manager for JNLP environment. This security manager @@ -48,7 +43,7 @@ */ class JNLPSecurityManager extends SecurityManager { - private final static Logger LOG = LoggerFactory.getLogger(JNLPSecurityManager.class); + private static final Logger LOG = LoggerFactory.getLogger(JNLPSecurityManager.class); // todo: some apps like JDiskReport can close the VM even when // an exit class is set - fix! @@ -81,29 +76,15 @@ class JNLPSecurityManager extends SecurityManager { // another way for different apps to have different properties // in java.lang.System with the same names. - /** only class that can exit the JVM, if set */ - private Object exitClass = null; - - /** this exception prevents exiting the JVM */ - private SecurityException closeAppEx = // making here prevents huge stack traces - new SecurityException("This exception to prevent shutdown of JVM, but the process has been terminated."); - - /** weak list of windows created */ - private WeakList weakWindows = new WeakList(); - - /** weak list of applications corresponding to window list */ - private WeakList weakApplications = - new WeakList(); - - /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ - private boolean exitAllowed = true; + /** + * weak list of windows created + */ + private WeakList weakWindows = new WeakList<>(); /** - * The AppContext of the main application (netx). We need to store this here - * so we can return this when no code from an external application is - * running on the thread + * weak list of applications corresponding to window list */ - private AppContext mainAppContext; + private WeakList weakApplications = new WeakList<>(); /** * Creates a JNLP SecurityManager. @@ -119,56 +100,15 @@ class JNLPSecurityManager extends SecurityManager { SwingUtils.getOrCreateWindowOwner(); } - mainAppContext = AppContext.getAppContext(); - } - - /** - * Returns whether the exit class is present on the stack, or - * true if no exit class is set. - */ - public boolean isExitClass() { - return isExitClass(getClassContext()); - } - - /** - * Returns whether the exit class is present on the stack, or - * true if no exit class is set. - */ - private boolean isExitClass(Class stack[]) { - if (exitClass == null) { - return true; - } - - for (int i = 0; i < stack.length; i++) { - if (stack[i] == exitClass) { - return true; - } - } - - return false; - } - - /** - * Set the exit class, which is the only class that can exit the - * JVM; if not set then any class can exit the JVM. - * - * @param exitClass the exit class - * @throws IllegalStateException if the exit class is already set - */ - public void setExitClass(Class exitClass) throws IllegalStateException { - if (this.exitClass != null) { - throw new IllegalStateException("Exit class already set and caller is not exit class."); - } - - this.exitClass = exitClass; + AppContext.getAppContext(); } /** * Return the current Application, or null if none can be * determined. */ - protected ApplicationInstance getApplication() { - return getApplication(Thread.currentThread(), getClassContext(), 0); + protected Optional getApplication() { + return ApplicationManager.getApplication(); } /** @@ -176,7 +116,7 @@ protected ApplicationInstance getApplication() { * call from event dispatch thread). */ protected ApplicationInstance getApplication(Window window) { - for (int i = weakWindows.size(); i-- > 0;) { + for (int i = weakWindows.size(); i-- > 0; ) { Window w = weakWindows.get(i); if (w == null) { weakWindows.remove(i); @@ -191,73 +131,15 @@ protected ApplicationInstance getApplication(Window window) { return null; } - /** - * Return the current Application, or null. - */ - protected ApplicationInstance getApplication(Thread thread, Class stack[], int maxDepth) { - ClassLoader cl; - JNLPClassLoader jnlpCl; - - cl = thread.getContextClassLoader(); - while (cl != null) { - jnlpCl = getJnlpClassLoader(cl); - if (jnlpCl != null && jnlpCl.getApplication() != null) { - return jnlpCl.getApplication(); - } - cl = cl.getParent(); - } - - if (maxDepth <= 0) { - maxDepth = stack.length; - } - - // this needs to be tightened up - for (int i = 0; i < stack.length && i < maxDepth; i++) { - cl = stack[i].getClassLoader(); - while (cl != null) { - jnlpCl = getJnlpClassLoader(cl); - if (jnlpCl != null && jnlpCl.getApplication() != null) { - return jnlpCl.getApplication(); - } - cl = cl.getParent(); - } - } - return null; - } - - /** - * Returns the JNLPClassLoader associated with the given ClassLoader, or - * null. - * @param cl a ClassLoader - * @return JNLPClassLoader or null - */ - private JNLPClassLoader getJnlpClassLoader(ClassLoader cl) { - // Since we want to deal with JNLPClassLoader, extract it if this - // is a codebase loader - if (cl instanceof CodeBaseClassLoader) { - cl = ((CodeBaseClassLoader) cl).getParentJNLPClassLoader(); - } - - if (cl instanceof JNLPClassLoader) { - JNLPClassLoader loader = (JNLPClassLoader) cl; - return loader; - } - - return null; - } - /** * Returns the application's thread group if the application can * be determined; otherwise returns super.getThreadGroup() */ @Override public ThreadGroup getThreadGroup() { - ApplicationInstance app = getApplication(); - if (app == null) { - return super.getThreadGroup(); - } - - return app.getThreadGroup(); + return getApplication() + .map(ApplicationInstance::getThreadGroup) + .orElse(super.getThreadGroup()); } /** @@ -274,160 +156,4 @@ public void checkPermission(Permission perm) { throw ex; } } - - /** - * Asks the user whether or not to grant permission. - * @param perm the permission to be granted - * @return true if the permission was granted, false otherwise. - */ - private boolean askPermission(Permission perm) { - - ApplicationInstance app = getApplication(); - if (app != null && !app.isSigned()) { - if (perm instanceof SocketPermission - && ServiceUtil.checkAccess(AccessType.NETWORK, perm.getName())) { - return true; - } - } - - return false; - } - - /** - * Adds a permission to the JNLPClassLoader. - * @param perm the permission to add to the JNLPClassLoader - */ - private void addPermission(Permission perm) { - if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { - - JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); - cl.addPermission(perm); - if (JNLPRuntime.isDebug()) { - if (cl.getSecurity() == null) { - if (cl.getPermissions(null).implies(perm)){ - LOG.warn("Added permission: {}", perm); - } else { - LOG.warn("Unable to add permission: {}", perm); - } - } else { - LOG.warn("Cannot get permissions for null codesource when classloader security is not null"); - } - } - } else { - LOG.debug("Unable to add permission: {}, classloader not JNLP.", perm); - } - } - - /** - * Checks whether the window can be displayed without an applet - * warning banner, and adds the window to the list of windows to - * be disposed when the calling application exits. - */ - @Override - public boolean checkTopLevelWindow(Object window) { - ApplicationInstance app = getApplication(); - - // remember window -> application mapping for focus, close on exit - if (app != null && window instanceof Window) { - Window w = (Window) window; - - LOG.debug("SM: app: {} is adding a window: {} with appContext {}", app.getTitle(), window, AppContext.getAppContext()); - - weakWindows.add(w); // for mapping window -> app - weakApplications.add(app); - - app.addWindow(w); - } - - // todo: set awt.appletWarning to custom message - // todo: logo on with glass pane on JFrame/JWindow? - - return super.checkTopLevelWindow(window); - } - - /** - * Checks whether the caller can exit the system. This method - * identifies whether the caller is a real call to Runtime.exec - * and has special behavior when returning from this method - * would exit the JVM and an exit class is set: if the caller is - * not the exit class then the calling application will be - * stopped and its resources destroyed (when possible), and an - * exception will be thrown to prevent the JVM from shutting - * down. - *

    - * Calls not from Runtime.exit or with no exit class set will - * behave normally, and the exit class can always exit the JVM. - *

    - */ - @Override - public void checkExit(int status) { - - // applets are not allowed to exit, but the plugin main class (primordial loader) is - Class stack[] = getClassContext(); - if (!exitAllowed) { - for (int i = 0; i < stack.length; i++) { - if (stack[i].getClassLoader() != null) { - throw new AccessControlException("Applets may not call System.exit()"); - } - } - } - - super.checkExit(status); - - boolean realCall = (stack[1] == Runtime.class); - - if (isExitClass(stack)) { - return; - } // to Runtime.exit or fake call to see if app has permission - - // not called from Runtime.exit() - if (!realCall) { - // apps that can't exit should think they can exit normally - super.checkExit(status); - return; - } - - // but when they really call, stop only the app instead of the JVM - ApplicationInstance app = getApplication(Thread.currentThread(), stack, 0); - if (app == null) { - throw new SecurityException("Cannot exit the JVM because the current application cannot be determined."); - } - - app.destroy(); - - throw closeAppEx; - } - - protected void disableExit() { - exitAllowed = false; - } - - /** - * Tests if a client can get access to the AWT event queue. This version allows - * complete access to the EventQueue for its own AppContext-specific EventQueue. - * - * FIXME there are probably huge security implications for this. Eg: - * http://hg.openjdk.java.net/jdk7/awt/jdk/rev/8022709a306d - * - * @exception SecurityException if the caller does not have - * permission to access the AWT event queue. - */ - @Override - public void checkAwtEventQueueAccess() { - /* - * this is the template of the code that should allow applets access to - * eventqueues - */ - - // AppContext appContext = AppContext.getAppContext(); - // ApplicationInstance instance = getApplication(); - - // if ((appContext == mainAppContext) && (instance != null)) { - // If we're about to allow access to the main EventQueue, - // and anything untrusted is on the class context stack, - // disallow access. - super.checkAwtEventQueueAccess(); - // } - } - } diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegate.java b/core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegate.java similarity index 69% rename from core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegate.java rename to core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegate.java index 6bead9097..9716a3dfc 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegate.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegate.java @@ -1,10 +1,7 @@ -package net.sourceforge.jnlp.runtime.classloader; +package net.sourceforge.jnlp.runtime; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.sourceforge.jnlp.LaunchException; -import java.net.URL; import java.security.Permission; import java.util.Collection; @@ -22,12 +19,6 @@ */ public interface SecurityDelegate { - SecurityDesc getCodebaseSecurityDesc(final JARDesc jarDesc, final URL codebaseHost); - - SecurityDesc getClassLoaderSecurity(final URL codebaseHost) throws LaunchException; - - SecurityDesc getJarPermissions(final URL codebaseHost); - void promptUserOnPartialSigning() throws LaunchException; void setRunInSandbox() throws LaunchException; diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegateNew.java b/core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegateNew.java new file mode 100644 index 000000000..ffcf8f81f --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/SecurityDelegateNew.java @@ -0,0 +1,90 @@ +package net.sourceforge.jnlp.runtime; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; +import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.config.ConfigurationConstants; + +import java.security.Permission; +import java.util.Collection; + +import static net.sourceforge.jnlp.LaunchException.FATAL; + +/** + * Various combinations of the jars being signed and tags being + * present are possible. They are treated as follows + * + * Jars JNLP File Result + * + * Signed Appropriate Permissions + * Signed no Sandbox + * Unsigned Error + * Unsigned no Sandbox + * + */ +public class SecurityDelegateNew implements SecurityDelegate { + + private static final Logger LOG = LoggerFactory.getLogger(SecurityDelegateNew.class); + + private boolean runInSandbox; + private boolean promptedForPartialSigning; + + private final ApplicationPermissions applicationPermissions; + + private final JNLPFile jnlpFile; + + public SecurityDelegateNew(final ApplicationPermissions applicationPermissions, final JNLPFile jnlpFile) { + this.applicationPermissions = applicationPermissions; + this.jnlpFile = jnlpFile; + runInSandbox = false; + } + + static void consultCertificateSecurityException(LaunchException ex) throws LaunchException { + if (isCertUnderestimated()) { + LOG.error("{} and {} are declared. Ignoring certificate issue", CommandLineOptions.NOSEC.getOption(), ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES); + } else { + throw ex; + } + } + + private static boolean isCertUnderestimated() { + return Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES)) + && !JNLPRuntime.isSecurityEnabled(); + } + + @Override + public void setRunInSandbox() throws LaunchException { + if (runInSandbox && !applicationPermissions.getAllJarLocations().isEmpty()) { + throw new LaunchException(jnlpFile, null, FATAL, "Initialization Error", "Run in Sandbox call performed too late.", "The classloader was notified to run the applet sandboxed, but security settings were already initialized."); + } + + // TODO: refresh policy to make sure we have the latest and greatest from the file system + this.runInSandbox = true; + } + + @Override + public void promptUserOnPartialSigning() throws LaunchException { + if (promptedForPartialSigning) { + return; + } + promptedForPartialSigning = true; + // TODO: the following line will trigger a NPE further down in the call + UnsignedAppletTrustConfirmation.checkPartiallySignedWithUserIfRequired(this, jnlpFile, null); + } + + @Override + public boolean getRunInSandbox() { + return this.runInSandbox; + } + + @Override + public void addPermissions(final Collection perms) { + for (final Permission perm : perms) { + applicationPermissions.addRuntimePermission(perm); + } + } + +} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoader.java deleted file mode 100644 index 4b3a6d0d9..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoader.java +++ /dev/null @@ -1,155 +0,0 @@ -package net.sourceforge.jnlp.runtime.classloader; - -import net.sourceforge.jnlp.NullJnlpFileException; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; - -/** - * ... - */ /* - * Helper class to expose protected URLClassLoader methods. - * Classes loaded from the codebase are absolutely NOT signed, by definition! - * If the CodeBaseClassLoader is used to load any classes in JNLPClassLoader, - * then you *MUST* check if the JNLPClassLoader is set to FULL signing. If so, - * then it must be set instead to PARTIAL, and the user prompted if it is okay - * to proceed. If the JNLPClassLoader is already PARTIAL or NONE signing, then - * nothing must be done. This is required so that we can support partial signing - * of applets but also ensure that using codebase loading in conjunction with - * signed JARs still results in the user having to confirm that this is - * acceptable. - */ -public class CodeBaseClassLoader extends URLClassLoader { - - JNLPClassLoader parentJNLPClassLoader; - - /** - * Classes that are not found, so that findClass can skip them next time - */ - ConcurrentHashMap notFoundResources = new ConcurrentHashMap<>(); - - CodeBaseClassLoader(URL[] urls, JNLPClassLoader cl) { - super(urls, cl); - parentJNLPClassLoader = cl; - } - - @Override - public void addURL(URL url) { - super.addURL(url); - } - - /* - * Use with care! Check the class-level Javadoc before calling this. - */ - Class findClassNonRecursive(final String name) throws ClassNotFoundException { - // If we have searched this path before, don't try again - if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) { - throw new ClassNotFoundException(name); - } - - try { - return AccessController.doPrivileged( - (PrivilegedExceptionAction>) () -> { - Class c = CodeBaseClassLoader.super.findClass(name); - parentJNLPClassLoader.checkPartialSigningWithUser(); - return c; - }, parentJNLPClassLoader.getAccessControlContextForClassLoading()); - } catch (PrivilegedActionException pae) { - notFoundResources.put(name, super.getURLs()); - throw new ClassNotFoundException("Could not find class " + name, pae); - } catch (NullJnlpFileException njf) { - notFoundResources.put(name, super.getURLs()); - throw new ClassNotFoundException("Could not find class " + name, njf); - } - } - - /* - * Use with care! Check the class-level Javadoc before calling this. - */ - @Override - public Class findClass(String name) throws ClassNotFoundException { - // Calls JNLPClassLoader#findClass which may call into this.findClassNonRecursive - Class c = getParentJNLPClassLoader().findClass(name); - parentJNLPClassLoader.checkPartialSigningWithUser(); - return c; - } - - /** - * Returns the output of super.findLoadedClass(). - *

    - * The method is renamed because ClassLoader.findLoadedClass() is final - * - * @param name The name of the class to find - * @return Output of ClassLoader.findLoadedClass() which is the class if - * found, null otherwise - * @see ClassLoader#findLoadedClass(String) - */ - Class findLoadedClassFromParent(String name) { - return findLoadedClass(name); - } - - /** - * Returns JNLPClassLoader that encompasses this loader - * - * @return parent JNLPClassLoader - */ - public JNLPClassLoader getParentJNLPClassLoader() { - return parentJNLPClassLoader; - } - - @Override - public Enumeration findResources(String name) throws IOException { - - // If we have searched this path before, don't try again - if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) { - return (new Vector(0)).elements(); - } - - if (!name.startsWith("META-INF")) { - Enumeration urls = super.findResources(name); - - if (!urls.hasMoreElements()) { - notFoundResources.put(name, super.getURLs()); - } - - return urls; - } - - return (new Vector(0)).elements(); - } - - @Override - public URL findResource(String name) { - - // If we have searched this path before, don't try again - if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) { - return null; - } - - URL url = null; - if (!name.startsWith("META-INF")) { - try { - final String fName = name; - url = AccessController.doPrivileged( - (PrivilegedExceptionAction) () -> CodeBaseClassLoader.super.findResource(fName), parentJNLPClassLoader.getAccessControlContextForClassLoading()); - } catch (PrivilegedActionException ignored) { - } - - if (url == null) { - notFoundResources.put(name, super.getURLs()); - } - - return url; - } - - return null; - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/DelegatingClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/DelegatingClassLoader.java deleted file mode 100644 index 37c82fc20..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/DelegatingClassLoader.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.sourceforge.jnlp.runtime.classloader; - -import net.adoptopenjdk.icedteaweb.Assert; - -import java.net.URL; -import java.util.Objects; - -public class DelegatingClassLoader extends ClassLoader { - public static final DelegatingClassLoader instance = new DelegatingClassLoader(Thread.currentThread().getContextClassLoader()); - private ClassLoader classLoader; - - public static DelegatingClassLoader getInstance() { - return instance; - } - - private DelegatingClassLoader(ClassLoader loader) { - super(Assert.requireNonNull(loader, "loader")); - this.classLoader = loader; - } - - public void setClassLoader(ClassLoader loader) { - this.classLoader = Assert.requireNonNull(loader, "loader"); - } - - protected Class findClass(String name) throws ClassNotFoundException { - return this.classLoader.loadClass(name); - } - - protected URL findResource(String name) { - return this.classLoader.getResource(name); - } - - public int hashCode() { - int result = 1; - result = 31 * result + (this.classLoader == null ? 0 : this.classLoader.hashCode()); - result = 31 * result + (this.getParent() == null ? 0 : this.getParent().hashCode()); - return result; - } - - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - else if (obj == null) { - return false; - } - else if (this.getClass() != obj.getClass()) { - return false; - } - else { - DelegatingClassLoader other = (DelegatingClassLoader) obj; - if(!Objects.equals(this.classLoader,other.classLoader)) { - return false; - } - if(!Objects.equals(this.getParent(),other.getParent())) { - return false; - } - return true; - } - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java deleted file mode 100644 index 6830336d6..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java +++ /dev/null @@ -1,2137 +0,0 @@ -// -// 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.runtime.classloader; - -import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; -import net.adoptopenjdk.icedteaweb.http.CloseableConnection; -import net.adoptopenjdk.icedteaweb.http.ConnectionFactory; -import net.adoptopenjdk.icedteaweb.jdk89access.JarIndexAccess; -import net.adoptopenjdk.icedteaweb.jnlp.element.EntryPoint; -import net.adoptopenjdk.icedteaweb.jnlp.element.application.AppletDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.application.ApplicationDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ExtensionDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; -import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; -import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader; -import net.adoptopenjdk.icedteaweb.resources.IllegalResourceDescriptorException; -import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; -import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; -import net.adoptopenjdk.icedteaweb.resources.cache.Cache; -import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.JNLPFileFactory; -import net.sourceforge.jnlp.JNLPMatcher; -import net.sourceforge.jnlp.JNLPMatcherException; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.NullJnlpFileException; -import net.sourceforge.jnlp.ParserSettings; -import net.sourceforge.jnlp.cache.CacheUtil; -import net.sourceforge.jnlp.cache.NativeLibraryStorage; -import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.runtime.ApplicationInstance; -import net.sourceforge.jnlp.runtime.CachedJarFileCallback; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.security.AppVerifier; -import net.sourceforge.jnlp.security.JNLPAppVerifier; -import net.sourceforge.jnlp.tools.JarCertVerifier; -import net.sourceforge.jnlp.util.JarFile; -import net.sourceforge.jnlp.util.UrlUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FilePermission; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.SocketPermission; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.AccessControlContext; -import java.security.AccessControlException; -import java.security.AccessController; -import java.security.AllPermission; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.stream.Stream; - -import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; -import static net.sourceforge.jnlp.LaunchException.FATAL; -import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_DYLIB; -import static net.sourceforge.jnlp.cache.NativeLibraryStorage.NATIVE_LIB_EXT_JNILIB; -import static net.sourceforge.jnlp.util.UrlUtils.FILE_PROTOCOL; -import static sun.security.util.SecurityConstants.FILE_READ_ACTION; - -/** - * Classloader that takes it's resources from a JNLP file. If the JNLP file - * defines extensions, separate classloaders for these will be created - * automatically. Classes are loaded with the security context when the - * classloader was created. - * - * @author Jon A. Maxwell - * (JAM) - initial author - * @version $Revision: 1.20 $ - */ -public class JNLPClassLoader extends URLClassLoader { - - private static final Logger LOG = LoggerFactory.getLogger(JNLPClassLoader.class); - - // todo: initializePermissions should get the permissions from - // extension classes too so that main file classes can load - // resources in an extension. - /** - * Signed JNLP File and Template - */ - private static final String TEMPLATE = "JNLP-INF/APPLICATION_TEMPLATE.JNLP"; - private static final String APPLICATION = "JNLP-INF/APPLICATION.JNLP"; - - /** - * Actions to specify how cache is to be managed * - */ - enum DownloadAction { - DOWNLOAD_TO_CACHE, REMOVE_FROM_CACHE, CHECK_CACHE - } - - public enum SigningState { - FULL, PARTIAL, NONE - } - - /** - * True if the application has a signed JNLP File - */ - private boolean isSignedJNLP = false; - - /** - * map from JNLPFile unique key to shared classloader - */ - private static final Map uniqueKeyToLoader = new ConcurrentHashMap<>(); - - /** - * map from JNLPFile unique key to lock, the lock is needed to enforce - * correct initialization of applets that share a unique key - */ - private static final Map uniqueKeyToLock = new HashMap<>(); - - /** - * Provides a search path & temporary storage for native code - */ - private final NativeLibraryStorage nativeLibraryStorage; - - /** - * security context - */ - private final AccessControlContext acc = AccessController.getContext(); - - /** - * the permissions for the cached jar files - */ - private final List resourcePermissions; - - /** - * the app - */ - private ApplicationInstance app = null; // here for faster lookup in security manager - - /** - * list of this, local and global loaders this loader uses - */ - private JNLPClassLoader[] loaders = null; // ..[0]==this - - /** - * whether to strictly adhere to the spec or not - */ - private final boolean strict; - - /** - * loads the resources - */ - private final ResourceTracker tracker; - - /** - * the update policy for resources - */ - private final UpdatePolicy updatePolicy; - - /** - * the JNLP file - */ - private final JNLPFile file; - - /** - * the resources section - */ - private final ResourcesDesc resources; - - /** - * the security section - */ - private SecurityDesc security; - - /** - * Permissions granted by the user during runtime. - */ - private final ArrayList runtimePermissions = new ArrayList<>(); - - /** - * all jars not yet part of classloader or active Synchronized since this - * field may become shared data between multiple classloading threads. See - * loadClass(String) and CodebaseClassLoader.findClassNonRecursive(String). - */ - private final List available = Collections.synchronizedList(new ArrayList<>()); - - /** - * the jar cert verifier tool to verify our jars - */ - final JarCertVerifier jcv; - - private SigningState signing = SigningState.NONE; - - /** - * ArrayList containing jar indexes for various jars available to this - * classloader Synchronized since this field may become shared data between - * multiple classloading threads/ See loadClass(String) and - * CodebaseClassLoader.findClassNonRecursive(String). - */ - private final List jarIndexes = Collections.synchronizedList(new ArrayList<>()); - - /** - * Set of classpath strings declared in the manifest.mf files Synchronized - * since this field may become shared data between multiple classloading - * threads. See loadClass(String) and - * CodebaseClassLoader.findClassNonRecursive(String). - */ - private final Set classpaths = Collections.synchronizedSet(new HashSet<>()); - - /** - * File entries in the jar files available to this classloader Synchronized - * sinc this field may become shared data between multiple classloading - * threads. See loadClass(String) and - * CodebaseClassLoader.findClassNonRecursive(String). - */ - private final Set jarEntries = Collections.synchronizedSet(new TreeSet<>()); - - /** - * Map of specific original (remote) CodeSource Urls to securitydesc - * Synchronized since this field may become shared data between multiple - * classloading threads. See loadClass(String) and - * CodebaseClassLoader.findClassNonRecursive(String). - */ - final Map jarLocationSecurityMap = Collections.synchronizedMap(new HashMap<>()); - - /*Set to prevent once tried-to-get resources to be tried again*/ - private final Set alreadyTried = Collections.synchronizedSet(new HashSet<>()); - - /** - * Loader for codebase (which is a path, rather than a file) - */ - private CodeBaseClassLoader codeBaseLoader; - - /** - * True if the jar with the main class has been found - */ - private boolean foundMainJar = false; - - /** - * Name of the application's main class - */ - private String mainClass; - - /** - * Variable to track how many times this loader is in use - */ - private int useCount = 0; - - private boolean enableCodeBase; - - private final SecurityDelegate securityDelegate; - - private ManifestAttributesChecker mac; - - /** - * Create a new JNLPClassLoader from the specified file. - * - * @param file the JNLP file - * @param policy update policy of loader - * @throws net.sourceforge.jnlp.LaunchException if app can not be loaded - */ - public JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException { - this(file, policy, null, false); - } - - /** - * Create a new JNLPClassLoader from the specified file. - * - * @param file the JNLP file - * @param policy the UpdatePolicy for this class loader - * @param mainName name of the application's main class - * @param enableCodeBase switch whether this classloader can search in - * codebase or not - * @throws net.sourceforge.jnlp.LaunchException when need to kill an app - * comes. - */ - private JNLPClassLoader(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException { - super(new URL[0], JNLPClassLoader.class.getClassLoader()); - - LOG.info("New classloader: {}", file.getFileLocation()); - strict = Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_STRICT_JNLP_CLASSLOADER)); - - this.file = file; - this.tracker = new ResourceTracker(true, file.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy()); - this.updatePolicy = policy; - this.resources = file.getResources(); - - this.nativeLibraryStorage = new NativeLibraryStorage(tracker); - - this.mainClass = mainName; - - this.enableCodeBase = enableCodeBase; - - final AppVerifier verifier = new JNLPAppVerifier(); - - jcv = new JarCertVerifier(verifier); - - if (this.enableCodeBase) { - addToCodeBaseLoader(this.file.getCodeBase()); - } - - this.securityDelegate = new SecurityDelegateImpl(this); - - if (mainClass == null) { - final EntryPoint entryPoint = file.getEntryPointDesc(); - if (entryPoint instanceof ApplicationDesc || entryPoint instanceof AppletDesc) { - mainClass = entryPoint.getMainClass(); - } - } - resourcePermissions = new ArrayList<>(); - - // initialize extensions - initializeExtensions(); - - initializeResources(); - - // initialize permissions - initializeReadJarPermissions(); - - installShutdownHooks(); - - } - - private static boolean isCertUnderestimated() { - return Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES)) - && !JNLPRuntime.isSecurityEnabled(); - } - - static void consultCertificateSecurityException(LaunchException ex) throws LaunchException { - if (isCertUnderestimated()) { - LOG.error("{} and {} are declared. Ignoring certificate issue", CommandLineOptions.NOSEC.getOption(), ConfigurationConstants.KEY_SECURITY_ITW_IGNORECERTISSUES); - } else { - throw ex; - } - } - - /** - * Install JVM shutdown hooks to clean up resources allocated by this - * ClassLoader. - */ - private void installShutdownHooks() { - /* - * Delete only the native dir created by this classloader (if - * there is one). Other classloaders (parent, peers) will all - * cleanup things they created - */ - Runtime.getRuntime().addShutdownHook(new Thread(() -> nativeLibraryStorage.cleanupTemporaryFolder())); - } - - private void setSecurity() throws LaunchException { - URL codebase = UrlUtils.guessCodeBase(file); - this.security = securityDelegate.getClassLoaderSecurity(codebase); - } - - /** - * Gets the lock for a given unique key, creating one if it does not yet - * exist. This operation is atomic & thread-safe. - * - * @param uniqueKey the file whose unique key should be used - * @return the lock - */ - private static ReentrantLock getUniqueKeyLock(String uniqueKey) { - synchronized (uniqueKeyToLock) { - ReentrantLock storedLock = uniqueKeyToLock.get(uniqueKey); - - if (storedLock == null) { - storedLock = new ReentrantLock(); - uniqueKeyToLock.put(uniqueKey, storedLock); - } - - return storedLock; - } - } - - /** - * Creates a fully initialized JNLP classloader for the specified JNLPFile, - * to be used as an applet/application's classloader. In contrast, JNLP - * classloaders can also be constructed simply to merge its resources into - * another classloader. - * - * @param file the file to load classes for - * @param policy the update policy to use when downloading resources - * @param mainName Overrides the main class name of the application - */ - private static JNLPClassLoader createInstance(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException { - String uniqueKey = file.getUniqueKey(); - JNLPClassLoader baseLoader = uniqueKeyToLoader.get(uniqueKey); - JNLPClassLoader loader = new JNLPClassLoader(file, policy, mainName, enableCodeBase); - - // If security level is 'high' or greater, we must check if the user allows unsigned applets - // when the JNLPClassLoader is created. We do so here, because doing so in the constructor - // causes unwanted side-effects for some applets. However, if the loader has been tagged - // with "runInSandbox", then we do not show this dialog - since this tag indicates that - // the user was already shown a CertWarning dialog and has chosen to run the applet sandboxed. - // This means they've already agreed to running the applet and have specified with which - // permission level to do it! - if (loader.getSigningState() == SigningState.PARTIAL) { - loader.securityDelegate.promptUserOnPartialSigning(); - } - - // New loader init may have caused extensions to create a - // loader for this unique key. Check. - JNLPClassLoader extLoader = uniqueKeyToLoader.get(uniqueKey); - - if (extLoader != null && extLoader != loader) { - if (loader.getSigning() != extLoader.getSigning()) { - loader.securityDelegate.promptUserOnPartialSigning(); - } - loader.merge(extLoader); - extLoader.decrementLoaderUseCount(); // loader urls have been merged, ext loader is no longer used - } - - // loader is now current + ext. But we also need to think of - // the baseLoader - if (baseLoader != null && baseLoader != loader) { - loader.merge(baseLoader); - } - - return loader; - } - - /** - * Returns a JNLP classloader for the specified JNLP file. - * - * @param file the file to load classes for - * @param policy the update policy to use when downloading resources - * @param enableCodeBase true if codebase can be searched (ok for - * applets,false for apps) - * @return existing classloader. creates new if none reliable exists - * @throws net.sourceforge.jnlp.LaunchException when launch is doomed - */ - public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy, boolean enableCodeBase) throws LaunchException { - return getInstance(file, policy, null, enableCodeBase); - } - - /** - * Returns a JNLP classloader for the specified JNLP file. - * - * @param file the file to load classes for - * @param policy the update policy to use when downloading resources - * @param mainName Overrides the main class name of the application - * @param enableCodeBase ue if codebase can be searched (ok for - * applets,false for apps) - * @return existing classloader. creates new if none reliable exists - * @throws net.sourceforge.jnlp.LaunchException when launch is doomed - */ - private static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy, String mainName, boolean enableCodeBase) throws LaunchException { - JNLPClassLoader loader; - String uniqueKey = file.getUniqueKey(); - - synchronized (getUniqueKeyLock(uniqueKey)) { - JNLPClassLoader baseLoader = uniqueKeyToLoader.get(uniqueKey); - - // A null baseloader implies that no loader has been created - // for this codebase/jnlp yet. Create one. - if (baseLoader == null - || (file.isApplication() - && !baseLoader.getJNLPFile().getFileLocation().equals(file.getFileLocation()))) { - - loader = createInstance(file, policy, mainName, enableCodeBase); - } else { - // if key is same and locations match, this is the loader we want - if (!file.isApplication()) { - // If this is an applet, we do need to consider its loader - loader = new JNLPClassLoader(file, policy, mainName, enableCodeBase); - baseLoader.merge(loader); - } - loader = baseLoader; - } - - // loaders are mapped to a unique key. Only extensions and parent - // share a key, so it is safe to always share based on it - loader.incrementLoaderUseCount(); - - uniqueKeyToLoader.put(uniqueKey, loader); - } - - return loader; - } - - /** - * Returns a JNLP classloader for the JNLP file at the specified location. - * - * @param location the file's location - * @param uniqueKey key to manage applets/applications in shared vm - * @param version the file's version - * @param settings settings of parser - * @param policy the update policy to use when downloading resources - * @param mainName Overrides the main class name of the application - * @param enableCodeBase whether to enable codebase search or not - * @return classloader of this app - * @throws java.io.IOException when IO fails - * @throws ParseException when parsing fails - * @throws net.sourceforge.jnlp.LaunchException when launch is doomed - */ - private static JNLPClassLoader getInstance(final URL location, final String uniqueKey, final VersionString version, final ParserSettings settings, final UpdatePolicy policy, final String mainName, boolean enableCodeBase) - throws IOException, ParseException, LaunchException { - - JNLPClassLoader loader; - - synchronized (getUniqueKeyLock(uniqueKey)) { - loader = uniqueKeyToLoader.get(uniqueKey); - - if (loader == null || !location.equals(loader.getJNLPFile().getFileLocation())) { - final JNLPFile jnlpFile = new JNLPFileFactory().create(location, uniqueKey, version, settings, policy); - - loader = getInstance(jnlpFile, policy, mainName, enableCodeBase); - } - } - - return loader; - } - - /** - * Load the extensions specified in the JNLP file. - */ - private void initializeExtensions() { - final List exceptions = new ArrayList<>(); - final List loaderList = new ArrayList<>(); - loaderList.add(this); - - final ExtensionDesc[] extDescs = resources.getExtensions(); - if (extDescs != null) { - final String uniqueKey = this.getJNLPFile().getUniqueKey(); - for (ExtensionDesc ext : extDescs) { - try { - final JNLPClassLoader loader = getInstance(ext.getLocation(), uniqueKey, ext.getVersion(), file.getParserSettings(), updatePolicy, mainClass, enableCodeBase); - loaderList.add(loader); - } catch (Exception ex) { - exceptions.add(new Exception("Exception while initializing extension '" + ext.getLocation() + "'", ex)); - } - } - } - - if (exceptions.size() > 0) { - exceptions.forEach(e -> LOG.error(e.getMessage(), e.getCause())); - throw new RuntimeException(exceptions.get(0)); - } - - loaders = loaderList.toArray(new JNLPClassLoader[0]); - } - - /** - * Make permission objects for the classpath. - */ - private void initializeReadJarPermissions() { - - JARDesc[] jars = resources.getJARs(); - for (JARDesc jar : jars) { - Permission p = getReadPermission(jar); - - if (p == null) { - LOG.info("Unable to add permission for {}", jar.getLocation()); - } else { - resourcePermissions.add(p); - LOG.info("Permission added: {}", p.toString()); - } - } - } - - private Permission getReadPermission(JARDesc jar) { - final URL location = jar.getLocation(); - - if (CacheUtil.isCacheable(location)) { - final File cacheFile = tracker.getCacheFile(location); - if (cacheFile != null) { - return new FilePermission(cacheFile.getPath(), FILE_READ_ACTION); - } else { - LOG.debug("No cache file for cacheable resource '{}' found.", location); - return null; - } - } else { - // this is what URLClassLoader does - try (final CloseableConnection conn = ConnectionFactory.openConnection(location)) { - return conn.getPermission(); - } catch (IOException ioe) { - LOG.error("Exception while retrieving permissions from connection to " + location, ioe); - } - } - - // should try to figure out the permission - return null; - } - - /** - * Check if a described jar file is invalid - * - * @param jar the jar to check - * @return true if file exists AND is an invalid jar, false otherwise - */ - boolean isInvalidJar(JARDesc jar) { - File cacheFile = tracker.getCacheFile(jar.getLocation()); - if (cacheFile == null) { - return false;//File cannot be retrieved, do not claim it is an invalid jar - } - boolean isInvalid = false; - try { - JarFile jarFile = new JarFile(cacheFile.getAbsolutePath()); - jarFile.close(); - } catch (IOException ioe) { - //Catch a ZipException or any other read failure - isInvalid = true; - } - return isInvalid; - } - - /** - * Load all of the JARs used in this JNLP file into the ResourceTracker for - * downloading. - */ - private void initializeResources() throws LaunchException { - - final JARDesc[] jars = resources.getJARs(); - - if (jars.length == 0) { - LOG.debug("no jars defined in jnlp file '{}'", file.getSourceLocation()); - - if (loaders.length > 1) { - LOG.debug("Checking extensions of jnlp file '{}'", file.getSourceLocation()); - final boolean containsUnsigned = Stream.of(loaders).anyMatch(l -> !l.getSigning()); - if (containsUnsigned) { - LOG.debug("At least one extension for jnlp file '{}' contains unsigned content", file.getSourceLocation()); - //TODO: is NONE really right? We do not kn ow if it is NONE or PARTIAL.... - signing = SigningState.NONE; - } else { - LOG.debug("All extensions of jnlp file '{}' are fully signed", file.getSourceLocation()); - signing = SigningState.FULL; - } - } else { - LOG.debug("JNLP file {} does not contain any jars or extensions and therefore is marked as fully signed", file.getSourceLocation()); - signing = SigningState.FULL; - } - - //Check if main jar is found within extensions - foundMainJar = foundMainJar || hasMainInExtensions(); - - setSecurity(); - initializeManifestAttributesChecker(); - mac.checkAll(); - return; - } - - final List initialJars = new ArrayList<>(); - - for (JARDesc jar : jars) { - - available.add(jar); - - if (jar.isEager() || jar.isMain()) { - initialJars.add(jar); // regardless of part - } - // FIXME: this will trigger an eager download as the tracker is created with prefetch == true - tracker.addResource(jar.getLocation(), jar.getVersion(), - jar.isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE); - } - - //If there are no eager jars, initialize the first jar - if (initialJars.isEmpty()) { - initialJars.add(jars[0]); - } - - if (strict) { - fillInPartJars(initialJars); // add in each initial part's lazy jars - } - - waitForJars(initialJars); //download the jars first. - - if (JNLPRuntime.isVerifying()) { - try { - jcv.add(initialJars, tracker); - } catch (Exception e) { - //we caught an Exception from the JarCertVerifier class. - //Note: one of these exceptions could be from not being able - //to read the cacerts or trusted.certs files. - LOG.error("Exception while verifying jars", e); - LaunchException ex = new LaunchException(null, null, FATAL, - "Initialization Error", "A fatal error occurred while trying to verify jars.", "An exception has been thrown in class JarCertVerifier. Being unable to read the cacerts or trusted.certs files could be a possible cause for this exception.: " + e.getMessage()); - consultCertificateSecurityException(ex); - } - - //Case when at least one jar has some signing - if (jcv.isFullySigned()) { - signing = SigningState.FULL; - - // Check for main class in the downloaded jars, and check/verify signed JNLP fill - checkForMain(initialJars); - - // If jar with main class was not found, check available resources - while (!foundMainJar && !available.isEmpty()) { - addNextResource(); - } - - // If the jar with main class was not found, check extension - // jnlp's resources - foundMainJar = foundMainJar || hasMainInExtensions(); - - boolean externalAppletMainClass = file.getEntryPointDesc() != null && !foundMainJar && available.isEmpty(); - - // We do this check here simply to ensure that if there are no JARs at all, - // and also no main-class in the codebase (ie the applet doesn't really exist), we - // fail ASAP rather than continuing (and showing the NotAllSigned dialog for no applet) - if (externalAppletMainClass) { - if (codeBaseLoader != null) { - try { - codeBaseLoader.findClass(mainClass); - } catch (ClassNotFoundException extCnfe) { - LOG.error("Could not determine the main class for this application.", extCnfe); - throw new LaunchException(file, extCnfe, FATAL, "Initialization Error", "Unknown Main-Class.", "Could not determine the main class for this application."); - } - } else { - throw new LaunchException(file, null, FATAL, "Initialization Error", "Unknown Main-Class.", "Could not determine the main class for this application."); - } - } - - // If externalAppletMainClass is true and a LaunchException was not thrown above, - // then the main-class can be loaded from the applet codebase, but is obviously not signed - if (externalAppletMainClass) { - checkPartialSigningWithUser(); - } - - // If main jar was found, but a signed JNLP file was not located - if (!isSignedJNLP && foundMainJar) { - file.setSignedJNLPAsMissing(); - } - - //user does not trust this publisher - if (!jcv.isTriviallySigned()) { - checkTrustWithUser(); - } - } else { - - // Otherwise this jar is simply unsigned -- make sure to ask - // for permission on certain actions - signing = SigningState.NONE; - } - } - setSecurity(); - - final Set validJars = new HashSet<>(); - boolean containsSignedJar = false, containsUnsignedJar = false; - for (JARDesc jarDesc : file.getResources().getJARs()) { - File cachedFile; - - try { - cachedFile = tracker.getCacheFile(jarDesc.getLocation()); - } catch (IllegalResourceDescriptorException irde) { - //Caused by ignored resource being removed due to not being valid - LOG.error("JAR " + jarDesc.getLocation() + " is not a valid jar file. Continuing.", irde); - continue; - } - - if (cachedFile == null) { - LOG.warn("initializeResource JAR {} not found. Continuing.", jarDesc.getLocation()); - continue; // JAR not found. Keep going. - } - - validJars.add(jarDesc); - final URL codebase = getJnlpFileCodebase(); - - final SecurityDesc jarSecurity = securityDelegate.getCodebaseSecurityDesc(jarDesc, codebase); - if (jarSecurity.getSecurityType().equals(SecurityDesc.SANDBOX_PERMISSIONS)) { - containsUnsignedJar = true; - } else { - containsSignedJar = true; - } - - if (containsUnsignedJar && containsSignedJar) { - signing = SigningState.PARTIAL; - break; - } - } - - if (containsSignedJar && containsUnsignedJar) { - checkPartialSigningWithUser(); - } - - setSecurity(); - - initializeManifestAttributesChecker(); - mac.checkAll(); - - for (JARDesc jarDesc : validJars) { - final URL codebase = getJnlpFileCodebase(); - final SecurityDesc jarSecurity = securityDelegate.getCodebaseSecurityDesc(jarDesc, codebase); - jarLocationSecurityMap.put(jarDesc.getLocation(), jarSecurity); - } - - activateJars(initialJars); - } - - private void initializeManifestAttributesChecker() { - if (mac == null) { - file.getManifestAttributesReader().setLoader(this); - mac = new ManifestAttributesChecker(security, file, signing, securityDelegate); - } - } - - private URL getJnlpFileCodebase() { - final URL codebase; - if (file.getCodeBase() != null) { - codebase = file.getCodeBase(); - } else { - // FIXME: codebase should be the codebase of the Main Jar not - // the location. Although, it still works in the current state. - codebase = file.getResources().getMainJAR().getLocation(); - } - return codebase; - } - - /** - * * - * Checks for the jar that contains the main class. If the main class was - * found, it checks to see if the jar is signed and whether it contains a - * signed JNLP file - * - * @param jars Jars that are checked to see if they contain the main class - * @throws LaunchException Thrown if the signed JNLP file, within the main - * jar, fails to be verified or does not match - */ - void checkForMain(List jars) throws LaunchException { - - // Check launch info - if (mainClass == null) { - final EntryPoint entryPoint = file.getEntryPointDesc(); - if (entryPoint != null) { - mainClass = entryPoint.getMainClass(); - } - } - - // The main class may be specified in the manifest - if (mainClass == null) { - mainClass = ManifestAttributesReader.getAttributeFromJars(Attributes.Name.MAIN_CLASS, jars, tracker); - } - - final String desiredJarEntryName = mainClass + ".class"; - - for (JARDesc jar : jars) { - - try { - final File localFile = tracker.getCacheFile(jar.getLocation()); - - if (localFile == null) { - LOG.warn("checkForMain JAR {} not found. Continuing.", jar.getLocation()); - continue; // JAR not found. Keep going. - } - - final JarFile jarFile = new JarFile(localFile); - - for (JarEntry entry : Collections.list(jarFile.entries())) { - String jeName = entry.getName().replaceAll("/", "."); - if (jeName.equals(desiredJarEntryName)) { - foundMainJar = true; - verifySignedJNLP(jarFile); - break; - } - } - - jarFile.close(); - } catch (IOException e) { - /* - * After this exception is caught, it is escaped. This will skip - * the jarFile that may have thrown this exception and move on - * to the next jarFile (if there are any) - */ - } - } - } - - /** - * @return true if this loader has the main jar - */ - boolean hasMainJar() { - return this.foundMainJar; - } - - /** - * Returns true if extension loaders have the main jar - */ - private boolean hasMainInExtensions() { - boolean foundMain = false; - - for (int i = 1; i < loaders.length && !foundMain; i++) { - foundMain = loaders[i].hasMainJar(); - } - - return foundMain; - } - - /** - * Is called by checkForMain() to check if the jar file is signed and if it - * contains a signed JNLP file. - * - * @param jarFile the jar file - * @throws LaunchException thrown if the signed JNLP file, within the main - * jar, fails to be verified or does not match - */ - private void verifySignedJNLP(JarFile jarFile) throws LaunchException { - try { - // NOTE: verification should have happened by now. In other words, - // calling jcv.verifyJars(desc, tracker) here should have no affect. - if (jcv.isFullySigned()) { - - for (JarEntry je : Collections.list(jarFile.entries())) { - String jeName = je.getName().toUpperCase(); - - if (jeName.equals(TEMPLATE) || jeName.equals(APPLICATION)) { - LOG.debug("Creating Jar InputStream from JarEntry"); - InputStream inStream = jarFile.getInputStream(je); - LOG.debug("Creating File InputStream from launching JNLP file"); - JNLPFile jnlp = this.getJNLPFile(); - File jn; - // If the file is on the local file system, use original path, otherwise find cached file - if (jnlp.getFileLocation().getProtocol().toLowerCase().equals(FILE_PROTOCOL)) { - jn = new File(jnlp.getFileLocation().getPath()); - } else { - jn = Cache.getCacheFile(jnlp.getFileLocation(), jnlp.getFileVersion()); - } - - InputStream jnlpStream = new FileInputStream(jn); - JNLPMatcher matcher; - if (jeName.equals(APPLICATION)) { // If signed application was found - LOG.debug("APPLICATION.JNLP has been located within signed JAR. Starting verification..."); - matcher = new JNLPMatcher(inStream, jnlpStream, false, jnlp.getParserSettings()); - } else { // Otherwise template was found - LOG.debug("APPLICATION_TEMPLATE.JNLP has been located within signed JAR. Starting verification..."); - matcher = new JNLPMatcher(inStream, jnlpStream, true, jnlp.getParserSettings()); - } - // If signed JNLP file does not matches launching JNLP file, throw JNLPMatcherException - if (!matcher.isMatch()) { - throw new JNLPMatcherException("Signed Application did not match launching JNLP File"); - } - - this.isSignedJNLP = true; - LOG.debug("Signed Application Verification Successful"); - - break; - } - } - } - } catch (JNLPMatcherException e) { - - /* - * Throws LaunchException if signed JNLP file fails to be verified - * or fails to match the launching JNLP file - */ - LaunchException ex = new LaunchException(file, null, FATAL, "Application Error", - "The signed JNLP file did not match the launching JNLP file.", R(e.getMessage())); - consultCertificateSecurityException(ex); - /* - * Throwing this exception will fail to initialize the application - * resulting in the termination of the application - */ - - } catch (Exception e) { - LOG.error("failed to validate the JNLP file itself", e); - - /* - * After this exception is caught, it is escaped. If an exception is - * thrown while handling the jar file, (mainly for - * JarCertVerifier.add) it assumes the jar file is unsigned and - * skip the check for a signed JNLP file - */ - } - LOG.debug("Ending check for signed JNLP file..."); - } - - /** - * Prompt the user for trust on all the signers that require approval. - * - * @throws LaunchException if the user does not approve every dialog prompt. - */ - private void checkTrustWithUser() throws LaunchException { - - if (securityDelegate.getRunInSandbox()) { - return; - } - - if (getSigningState() == SigningState.FULL && jcv.isFullySigned() && !jcv.getAlreadyTrustPublisher()) { - jcv.checkTrustWithUser(securityDelegate, file); - } - } - - /** - * Add applet's codebase URL. This allows compatibility with applets that - * load resources from their codebase instead of through JARs, but can slow - * down resource loading. Resources loaded from the codebase are not cached. - */ - public void enableCodeBase() { - addToCodeBaseLoader(file.getCodeBase()); - } - - /** - * Sets the JNLP app this group is for; can only be called once. - * - * @param app application to be ser to this group - */ - public void setApplication(ApplicationInstance app) { - if (this.app != null) { - LOG.error("Application can only be set once"); - return; - } - - this.app = app; - } - - /** - * @return the JNLP app for this classloader - */ - public ApplicationInstance getApplication() { - return app; - } - - /** - * @return the JNLP file the classloader was created from. - */ - public JNLPFile getJNLPFile() { - return file; - } - - /** - * Returns the permissions for the CodeSource. - */ - @Override - public PermissionCollection getPermissions(CodeSource cs) { - try { - Permissions result = new Permissions(); - - // should check for extensions or boot, automatically give all - // access w/o security dialog once we actually check certificates. - // copy security permissions from SecurityDesc element - if (security != null) { - // Security desc. is used only to track security settings for the - // application. However, an application may comprise of multiple - // jars, and as such, security must be evaluated on a per jar basis. - - // set default perms - PermissionCollection permissions = security.getSandBoxPermissions(); - - // If more than default is needed: - // 1. Code must be signed - // 2. ALL or J2EE permissions must be requested (note: plugin requests ALL automatically) - if (cs == null) { - throw new NullPointerException("Code source was null"); - } - if (cs.getCodeSigners() != null) { - if (cs.getLocation() == null) { - throw new NullPointerException("Code source location was null"); - } - if (getCodeSourceSecurity(cs.getLocation()) == null) { - throw new NullPointerException("Code source security was null"); - } - if (getCodeSourceSecurity(cs.getLocation()).getSecurityType() == null) { - LOG.error("Warning! Code source security type was null"); - } - Object securityType = getCodeSourceSecurity(cs.getLocation()).getSecurityType(); - if (SecurityDesc.ALL_PERMISSIONS.equals(securityType) - || SecurityDesc.J2EE_PERMISSIONS.equals(securityType)) { - - permissions = getCodeSourceSecurity(cs.getLocation()).getPermissions(cs); - } - } - - for (Permission perm : Collections.list(permissions.elements())) { - result.add(perm); - } - } - - // add in permission to read the cached JAR files - for (Permission perm : resourcePermissions) { - result.add(perm); - } - - // add in the permissions that the user granted. - for (Permission perm : runtimePermissions) { - result.add(perm); - } - - // Class from host X should be allowed to connect to host X - if (cs.getLocation() != null && cs.getLocation().getHost().length() > 0) { - result.add(new SocketPermission(UrlUtils.getHostAndPort(cs.getLocation()), - "connect, accept")); - } - - return result; - } catch (RuntimeException ex) { - LOG.error("Failed to get permissions", ex); - throw ex; - } - } - - public void addPermission(Permission p) { - runtimePermissions.add(p); - } - - /** - * Adds to the specified list of JARS any other JARs that need to be loaded - * at the same time as the JARs specified (ie, are in the same part). - * - * @param jars jar archives to be added - */ - private void fillInPartJars(List jars) { - final LinkedHashSet result = new LinkedHashSet<>(); - for (JARDesc jar : jars) { - result.addAll(getAllAvailableJarsInPart(jar.getPart())); - result.remove(jar); - } - - jars.addAll(result); - } - - private LinkedHashSet getAllAvailableJarsInPart(String part) { - final LinkedHashSet jars = new LinkedHashSet<>(); - - // "available" field can be affected by two different threads - // working in loadClass(String) - if (part != null) { - synchronized (available) { - for (JARDesc jar : available) { - if (part.equals(jar.getPart())) { - jars.add(jar); - } - } - } - } - - return jars; - } - - /** - * Ensures that the list of jars have all been transferred, and makes them - * available to the classloader. If a jar contains native code, the - * libraries will be extracted and placed in the path. - * - * @param jars the list of jars to load - */ - void activateJars(final List jars) { - PrivilegedAction activate = () -> doActivateJars(jars); - AccessController.doPrivileged(activate, acc); - } - - private Void doActivateJars(List jars) { - // transfer the Jars - waitForJars(jars); - - for (JARDesc jar : jars) { - available.remove(jar); - - // add jar - File localFile = tracker.getCacheFile(jar.getLocation()); - try { - URL location = jar.getLocation(); // non-cacheable, use source location - if (localFile != null) { - location = localFile.toURI().toURL(); // cached file - // This is really not the best way.. but we need some way for - // PluginAppletViewer::getCachedImageRef() to check if the image - // is available locally, and it cannot use getResources() because - // that prefetches the resource, which confuses MediaTracker.waitForAll() - // which does a wait(), waiting for notification (presumably - // thrown after a resource is fetched). This bug manifests itself - // particularly when using The FileManager applet from Webmin. - try (JarFile jarFile = new JarFile(localFile)) { - for (JarEntry je : Collections.list(jarFile.entries())) { - - // another jar in my jar? it is more likely than you think - if (je.getName().endsWith(".jar")) { - // We need to extract that jar so that it can be loaded - // (inline loading with "jar:..!/..." path will not work - // with standard classloader methods) - - String name = je.getName(); - if (name.contains("..")) { - name = CacheUtil.hex(name, name); - } - String extractedJarLocation = localFile + ".nested/" + name; - File parentDir = new File(extractedJarLocation).getParentFile(); - if (!parentDir.isDirectory() && !parentDir.mkdirs()) { - throw new RuntimeException("Unable to extract nested jar."); - } - FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation); - InputStream is = jarFile.getInputStream(je); - - byte[] bytes = new byte[1024]; - int read = is.read(bytes); - int fileSize = read; - while (read > 0) { - extractedJar.write(bytes, 0, read); - read = is.read(bytes); - fileSize += read; - } - - is.close(); - extractedJar.close(); - - // 0 byte file? skip - if (fileSize <= 0) { - continue; - } - - tracker.addResource(new File(extractedJarLocation).toURI().toURL(), (VersionString) null); - - URL codebase = file.getCodeBase(); - if (codebase == null) { - //FIXME: codebase should be the codebase of the Main Jar not - //the location. Although, it still works in the current state. - codebase = file.getResources().getMainJAR().getLocation(); - } - - final SecurityDesc jarSecurity = securityDelegate.getJarPermissions(codebase); - - try { - URL fileURL = new URL("file://" + extractedJarLocation); - // there is no remote URL for this, so lets fake one - URL fakeRemote = new URL(jar.getLocation().toString() + "!" + je.getName()); - CachedJarFileCallback.getInstance().addMapping(fakeRemote, fileURL); - addURL(fakeRemote); - - jarLocationSecurityMap.put(fakeRemote, jarSecurity); - - } catch (MalformedURLException mfue) { - LOG.error("Unable to add extracted nested jar to classpath", mfue); - } - } - - jarEntries.add(je.getName()); - } - } - } - - addURL(jar.getLocation()); - - // there is currently no mechanism to cache files per - // instance.. so only index cached files - if (localFile != null) { - CachedJarFileCallback.getInstance().addMapping(jar.getLocation(), localFile.toURI().toURL()); - - try (JarFile jarFile = new JarFile(localFile.getAbsolutePath())) { - JarIndexAccess index = JarIndexAccess.getJarIndex(jarFile); - if (index != null) { - jarIndexes.add(index); - } - } - } else { - CachedJarFileCallback.getInstance().addMapping(jar.getLocation(), jar.getLocation()); - } - - LOG.debug("Activate jar: {}", location); - } catch (Exception ex) { - LOG.error("Error while activating jars", ex); - } - - // some programs place a native library in any jar - nativeLibraryStorage.addSearchJar(jar.getLocation()); - } - - return null; - } - - /** - * Return the absolute path to the native library. - */ - @Override - protected String findLibrary(String lib) { - String syslib = System.mapLibraryName(lib); - final String nativeLibrary = findNativeLibrary(lib, syslib); - // required for macOS, as of - // https://bugs.openjdk.java.net/browse/JDK-8127215 - // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7158157 - if (nativeLibrary == null && syslib.endsWith(NATIVE_LIB_EXT_DYLIB)) { - return findNativeLibrary(lib, "lib" + lib + NATIVE_LIB_EXT_JNILIB); - } - return nativeLibrary; - } - - private String findNativeLibrary(final String lib, final String syslib) { - File libFile = nativeLibraryStorage.findLibrary(syslib); - - if (libFile != null) { - return libFile.toString(); - } - - String result = super.findLibrary(lib); - if (result != null) { - return result; - } - - return findLibraryExt(lib); - } - - /** - * Try to find the library path from another peer classloader. - * - * @param lib library to be found - * @return location of library - */ - private String findLibraryExt(String lib) { - for (JNLPClassLoader loader : loaders) { - String result = null; - - if (loader != this) { - result = loader.findLibrary(lib); - } - - if (result != null) { - return result; - } - } - - return null; - } - - /** - * Wait for a group of JARs, and send download events if there is a download - * listener or display a progress window otherwise. - * - * @param jars the jars - */ - private void waitForJars(List jars) { - URL[] urls = new URL[jars.size()]; - - for (int i = 0; i < jars.size(); i++) { - JARDesc jar = jars.get(i); - - urls[i] = jar.getLocation(); - } - - CacheUtil.waitForResources(this, tracker, urls, file.getTitle()); - } - - /** - * Find the loaded class in this loader or any of its extension loaders. - * - * @param name name of class - * @return the class found by name - */ - private Class findLoadedClassAll(String name) { - for (JNLPClassLoader loader : loaders) { - Class result; - - if (loader == this) { - result = JNLPClassLoader.super.findLoadedClass(name); - } else { - result = loader.findLoadedClassAll(name); - } - - if (result != null) { - return result; - } - } - - // Result is still null. Return what the codebase loader - // has (which returns null if it is not loaded there either) - if (codeBaseLoader != null) { - return codeBaseLoader.findLoadedClassFromParent(name); - } else { - return null; - } - } - - @FunctionalInterface - public interface ExceptionalSupplier { - - T call() throws E; - - default T getResultOfCallOrNull() { - try { - return call(); - } catch (Exception e) { - return null; - } - } - } - - /** - * Find a JAR in the shared 'extension' classloaders, this classloader, or - * one of the classloaders for the JNLP file's extensions. This method used - * to be qualified "synchronized." This was done solely for the purpose of - * ensuring only one thread entered the method at a time. This was not - * strictly necessary - ensuring that all affected fields are thread-safe is - * sufficient. Locking on the JNLPClassLoader instance when this method is - * called can result in deadlock if another thread is dealing with the - * CodebaseClassLoader at the same time. This solution is very heavy-handed - * as the instance lock is not truly required, and taking the lock on the - * classloader instance when not needed is not in general a good idea - * because it can and will lead to deadlock when multithreaded classloading - * is in effect. The solution is to keep the fields thread safe on their - * own. This is accomplished by wrapping them in Collections.synchronized* - * to provide atomic add/remove operations, and synchronizing on them when - * iterating or performing multiple mutations. See bug report RH976833. On - * some systems this bug will manifest itself as deadlock on every webpage - * with more than one Java applet, potentially also causing the browser - * process to hang. More information in the mailing list archives: - * http://mail.openjdk.java.net/pipermail/distro-pkg-dev/2013-September/024536.html - *

    - * Affected fields: available, classpaths, jarIndexes, jarEntries, - * jarLocationSecurityMap - */ - @Override - public Class loadClass(final String name) throws ClassNotFoundException { - final List, ClassNotFoundException>> list = new ArrayList<>(); - list.add(() -> findLoadedClassAll(name)); - list.add(() -> loadClassFromParentClassloader(name)); - list.add(() -> loadClassExt(name)); - list.add(() -> loadClassFromInternalManifestClasspath(name)); - list.add(() -> loadFromJarIndexes(name)); - - return list.stream() - .map(ExceptionalSupplier::getResultOfCallOrNull) - .filter(Objects::nonNull) - .findFirst() - .orElseThrow(() -> new ClassNotFoundException(name)); - } - - private Class loadClassFromParentClassloader(final String name) throws ClassNotFoundException { - // try parent classloader - ClassLoader parent = getParent(); - if (parent == null) { - parent = ClassLoader.getSystemClassLoader(); - } - return parent.loadClass(name); - } - - private Class loadClassFromInternalManifestClasspath(final String name) throws ClassNotFoundException { - // Look in 'Class-Path' as specified in the manifest file - - // This field synchronized before iterating over it since it may - // be shared data between threads - synchronized (classpaths) { - for (String classpath : classpaths) { - JARDesc desc; - try { - URL jarUrl = new URL(file.getCodeBase(), classpath); - desc = new JARDesc(jarUrl, null, null, false, true, false, true); - } catch (MalformedURLException mfe) { - throw new ClassNotFoundException(name, mfe); - } - addNewJar(desc); - } - } - - return loadClassExt(name); - } - - private Class loadFromJarIndexes(final String name) throws ClassNotFoundException { - // As a last resort, look in any available indexes - // Currently this loads jars directly from the site. We cannot cache it because this - // call is initiated from within the applet, which does not have disk read/write permissions - // This field synchronized before iterating over it since it may - // be shared data between threads - synchronized (jarIndexes) { - for (JarIndexAccess index : jarIndexes) { - // Non-generic code in sun.misc.JarIndex - LinkedList jarList = index.get(name.replace('.', '/')); - - if (jarList != null) { - for (String jarName : jarList) { - try { - final JARDesc desc = new JARDesc(new URL(file.getCodeBase(), jarName), - null, null, false, true, false, true); - addNewJar(desc); - } catch (MalformedURLException mfe) { - LOG.debug("encountered invalid URL for {} - {}", file.getCodeBase(), jarName); - } - } - - // If it still fails, let it error out - return loadClassExt(name); - } - } - } - throw new ClassNotFoundException(name); - } - - /** - * Adds a new JARDesc into this classloader. - *

    - * This will add the JARDesc into the resourceTracker and block until it is - * downloaded. - *

    - * - * @param desc the JARDesc for the new jar - */ - private void addNewJar(final JARDesc desc) { - this.addNewJar(desc, JNLPRuntime.getDefaultUpdatePolicy()); - } - - /** - * Adds a new JARDesc into this classloader. - * - * @param desc the JARDesc for the new jar - * @param updatePolicy the UpdatePolicy for the resource - */ - private void addNewJar(final JARDesc desc, UpdatePolicy updatePolicy) { - - available.add(desc); - - tracker.addResource(desc.getLocation(), - desc.getVersion(), - updatePolicy - ); - - // Give read permissions to the cached jar file - AccessController.doPrivileged((PrivilegedAction) () -> { - Permission p = getReadPermission(desc); - if (p != null) { - resourcePermissions.add(p); - } - return null; - }); - - final URL remoteURL = desc.getLocation(); - final URL cachedUrl = tracker.getCacheURL(remoteURL); // blocks till download - - available.remove(desc); // Resource downloaded. Remove from available list. - - try { - // Decide what level of security this jar should have - // The verification and security setting functions rely on - // having AllPermissions as those actions normally happen - // during initialization. We therefore need to do those - // actions as privileged. - AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - jcv.add(desc, tracker); - - checkTrustWithUser(); - - final SecurityDesc security = securityDelegate.getJarPermissions(file.getCodeBase()); - - jarLocationSecurityMap.put(remoteURL, security); - - return null; - }); - - addURL(remoteURL); - CachedJarFileCallback.getInstance().addMapping(remoteURL, cachedUrl); - - } catch (Exception e) { - // Do nothing. This code is called by loadClass which cannot - // throw additional exceptions. So instead, just ignore it. - // Exception => jar will not get added to classpath, which will - // result in CNFE from loadClass. - LOG.error("Failed to add jar " + desc.getLocation(), e); - } - } - - /** - * Find the class in this loader or any of its extension loaders. - */ - @Override - protected Class findClass(String name) throws ClassNotFoundException { - for (JNLPClassLoader loader : loaders) { - try { - if (loader == this) { - final String fName = name; - return AccessController.doPrivileged( - (PrivilegedExceptionAction>) () -> JNLPClassLoader.super.findClass(fName), getAccessControlContextForClassLoading()); - } else { - return loader.findClass(name); - } - } catch (ClassNotFoundException | PrivilegedActionException ignored) { - } catch (ClassFormatError cfe) { - LOG.error("Error while trying to find class", cfe); - } catch (NullJnlpFileException ex) { - throw new ClassNotFoundException(this.mainClass + " in main classloader ", ex); - } - } - - // Try codebase loader - if (codeBaseLoader != null) { - return codeBaseLoader.findClassNonRecursive(name); - } - - // All else failed. Throw CNFE - throw new ClassNotFoundException(name); - } - - /** - * Search for the class by incrementally adding resources to the classloader - * and its extension classloaders until the resource is found. - */ - private Class loadClassExt(String name) throws ClassNotFoundException { - // make recursive - addAvailable(); - - // find it - try { - return findClass(name); - } catch (ClassNotFoundException ignored) { - } - - // add resources until found - while (true) { - JNLPClassLoader addedTo; - - try { - addedTo = addNextResource(); - } catch (LaunchException e) { - - /* - * This method will never handle any search for the main class - * [It is handled in initializeResources()]. Therefore, this - * exception will never be thrown here and is escaped - */ - throw new IllegalStateException(e); - } - - if (addedTo == null) { - throw new ClassNotFoundException(name); - } - - try { - return addedTo.findClass(name); - } catch (ClassNotFoundException ignored) { - } - } - } - - /** - * Finds the resource in this, the parent, or the extension class loaders. - * - * @return a {@link URL} for the resource, or {@code null} if the resource - * could not be found. - */ - @Override - public URL findResource(String name) { - URL result = null; - - try { - Enumeration e = findResources(name); - if (e.hasMoreElements()) { - result = e.nextElement(); - } - } catch (IOException e) { - LOG.error("Failed to find resource", e); - } - - // If result is still null, look in the codebase loader - if (result == null && codeBaseLoader != null) { - result = codeBaseLoader.findResource(name); - } - - return result; - } - - /** - * Find the resources in this, the parent, or the extension class loaders. - * Load lazy resources if not found in current resources. - */ - @Override - public Enumeration findResources(String name) throws IOException { - Enumeration lresources = findResourcesBySearching(name); - - try { - // if not found, load all lazy resources; repeat search - while (!lresources.hasMoreElements() && addNextResource() != null) { - lresources = findResourcesBySearching(name); - } - } catch (LaunchException le) { - LOG.error("Failed to load resources", le); - } - - return lresources; - } - - /** - * Find the resources in this, the parent, or the extension class loaders. - */ - private Enumeration findResourcesBySearching(String name) throws IOException { - List lresources = new ArrayList<>(); - Enumeration e = null; - - for (JNLPClassLoader loader : loaders) { - // TODO check if this will blow up or not - // if loaders[1].getResource() is called, wont it call getResource() on - // the original caller? infinite recursion? - - if (loader == this) { - final String fName = name; - try { - e = AccessController.doPrivileged((PrivilegedExceptionAction>) () -> JNLPClassLoader.super.findResources(fName), getAccessControlContextForClassLoading()); - } catch (PrivilegedActionException ignored) { - } - } else { - e = loader.findResources(name); - } - - final Enumeration fURLEnum = e; - try { - lresources.addAll(AccessController.doPrivileged( - new PrivilegedExceptionAction>() { - @Override - public Collection run() { - List resources = new ArrayList<>(); - while (fURLEnum != null && fURLEnum.hasMoreElements()) { - resources.add(fURLEnum.nextElement()); - } - return resources; - } - }, getAccessControlContextForClassLoading())); - } catch (PrivilegedActionException ignored) { - } - } - - // Add resources from codebase (only if nothing was found above, - // otherwise the server will get hammered) - if (lresources.isEmpty() && codeBaseLoader != null) { - e = codeBaseLoader.findResources(name); - while (e.hasMoreElements()) { - lresources.add(e.nextElement()); - } - } - - return Collections.enumeration(lresources); - } - - /** - * Adds whatever resources have already been downloaded in the background. - */ - private void addAvailable() { - // go through available, check tracker for it and all of its - // part brothers being available immediately, add them. - - for (int i = 1; i < loaders.length; i++) { - loaders[i].addAvailable(); - } - } - - /** - * Adds the next unused resource to the classloader. That resource and all - * those in the same part will be downloaded and added to the classloader - * before returning. If there are no more resources to add, the method - * returns immediately. - * - * @return the classloader that resources were added to, or null - * @throws LaunchException Thrown if the signed JNLP file, within the main - * jar, fails to be verified or does not match - */ - private JNLPClassLoader addNextResource() throws LaunchException { - if (available.isEmpty()) { - for (int i = 1; i < loaders.length; i++) { - JNLPClassLoader result = loaders[i].addNextResource(); - - if (result != null) { - return result; - } - } - return null; - } - - final List jars = getNextJarsToLoad(); - - checkForMain(jars); - activateJars(jars); - - return this; - } - - private List getNextJarsToLoad() { - final JARDesc nextJar = available.get(0); - - final LinkedHashSet result = new LinkedHashSet<>(); - result.add(nextJar); - result.addAll(getAllAvailableJarsInPart(nextJar.getPart())); - - return new ArrayList<>(result); - } - - public boolean getSigning() { - return signing == SigningState.FULL; - } - - /** - * Call this when it's suspected that an applet's permission level may have - * just changed from Full Signing to Partial Signing. This will display a - * one-time prompt asking the user to confirm running the partially signed - * applet. Partially Signed applets always start off as appearing to be - * Fully Signed, and then during the initialization or loading process, we - * find that we actually need to demote the applet to Partial, either due to - * finding that not all of its JARs are actually signed, or because it needs - * to load something unsigned out of the codebase. - */ - void checkPartialSigningWithUser() { - if (signing == SigningState.FULL && JNLPRuntime.isVerifying()) { - signing = SigningState.PARTIAL; - try { - securityDelegate.promptUserOnPartialSigning(); - } catch (LaunchException e) { - throw new RuntimeException("The signed applet required loading of unsigned code from the codebase, " - + "which the user refused", e); - } - } - } - - private SigningState getSigningState() { - return signing; - } - - public SecurityDesc getSecurity() { - return security; - } - - /** - * Returns the security descriptor for given code source URL - * - * @param source the origin (remote) url of the code - * @return The SecurityDescriptor for that source - */ - private SecurityDesc getCodeSourceSecurity(URL source) { - SecurityDesc sec = jarLocationSecurityMap.get(source); - synchronized (alreadyTried) { - if (sec == null && !alreadyTried.contains(source)) { - alreadyTried.add(source); - //try to load the jar which is requesting the permissions, but was NOT downloaded by standard way - LOG.info("Application is trying to get permissions for {}, which was not added by standard way. Trying to download and verify!", source.toString()); - try { - JARDesc des = new JARDesc(source, null, null, false, false, false, false); - addNewJar(des); - sec = jarLocationSecurityMap.get(source); - } catch (Throwable t) { - LOG.error("Error while getting security", t); - sec = null; - } - } - } - if (sec == null) { - LOG.info("Error: No security instance for {}. The application may have trouble continuing", source.toString()); - } - return sec; - } - - /** - * Merges the code source/security descriptor mapping from another loader - * - * @param extLoader The loader form which to merge - * @throws SecurityException if the code is called from an untrusted source - */ - private void merge(JNLPClassLoader extLoader) { - - try { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new AllPermission()); - } - } catch (SecurityException se) { - throw new SecurityException("JNLPClassLoader() may only be called from trusted sources!"); - } - - // jars - for (URL u : extLoader.getURLs()) { - addURL(u); - } - - // Codebase - if (this.enableCodeBase) { - addToCodeBaseLoader(extLoader.file.getCodeBase()); - } - - // native search paths - for (File nativeDirectory : extLoader.nativeLibraryStorage.getSearchDirectories()) { - nativeLibraryStorage.addSearchDirectory(nativeDirectory); - } - - // security descriptors - synchronized (jarLocationSecurityMap) { - for (URL key : extLoader.jarLocationSecurityMap.keySet()) { - jarLocationSecurityMap.put(key, extLoader.jarLocationSecurityMap.get(key)); - } - } - } - - /** - * Adds the given path to the path loader - * - * @param u the path to add - * @throws IllegalArgumentException If the given url is not a path - */ - private void addToCodeBaseLoader(URL u) { - if (u == null) { - return; - } - - // Only paths may be added - if (!u.getFile().endsWith("/")) { - throw new IllegalArgumentException("addToPathLoader only accepts path based URLs"); - } - - // If there is no loader yet, create one, else add it to the - // existing one (happens when called from merge()) - if (codeBaseLoader == null) { - codeBaseLoader = new CodeBaseClassLoader(new URL[]{u}, this); - } else { - codeBaseLoader.addURL(u); - } - } - - - /** - * Increments loader use count by 1 - * - * @throws SecurityException if caller is not trusted - */ - private void incrementLoaderUseCount() { - - // For use by trusted code only - if (System.getSecurityManager() != null) { - System.getSecurityManager().checkPermission(new AllPermission()); - } - - // NB: There will only ever be one class-loader per unique-key - synchronized (getUniqueKeyLock(file.getUniqueKey())) { - useCount++; - } - } - - /** - * Returns all loaders that this loader uses, including itself - */ - JNLPClassLoader[] getLoaders() { - return loaders; - } - - /** - * Remove jars from the file system. - * - * @param jars Jars marked for removal. - */ - void removeJars(JARDesc[] jars) { - - for (JARDesc eachJar : jars) { - final URL location = eachJar.getLocation(); - final VersionString version = eachJar.getVersion(); - - try { - tracker.removeResource(location); - } catch (Exception e) { - LOG.error("Failed to remove resource from tracker, continuing..", e); - } - - Cache.deleteFromCache(location, version); - } - } - - /** - * Downloads and initializes jars into this loader. - * - * @param ref Path of the launch or extension JNLP File containing the - * resource. If null, main JNLP's file location will be used instead. - * @param part The name of the path. - * @param version of jar to be downloaded - */ - void initializeNewJarDownload(final URL ref, final String part, final VersionString version) { - final JARDesc[] jars = ManageJnlpResources.findJars(this, ref, part, version); - - for (JARDesc eachJar : jars) { - LOG.info("Downloading and initializing jar: {}", eachJar.getLocation().toString()); - - this.addNewJar(eachJar, UpdatePolicy.FORCE); - } - } - - /** - * Manages DownloadService jars which are not mentioned in the JNLP file - * - * @param ref Path to the resource. - * @param version The version of resource. If null, no version is specified. - * @param action The action to perform with the resource. Either - * DOWNLOADTOCACHE, REMOVEFROMCACHE, or CHECKCACHE. - * @return true if CHECKCACHE and the resource is cached. - */ - boolean manageExternalJars(final URL ref, final String version, final DownloadAction action) { - boolean approved = false; - JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByResourceUrl(this, ref, version); - final VersionString resourceVersion = (version == null) ? null : VersionString.fromString(version); - - if (foundLoader != null) { - approved = true; - } else if (ref.toString().startsWith(file.getNotNullProbableCodeBase().toString())) { - approved = true; - } else if (SecurityDesc.ALL_PERMISSIONS.equals(security.getSecurityType())) { - approved = true; - } - - if (approved) { - if (foundLoader == null) { - foundLoader = this; - } - - if (action == DownloadAction.DOWNLOAD_TO_CACHE) { - JARDesc jarToCache = new JARDesc(ref, resourceVersion, null, false, true, false, true); - LOG.info("Downloading and initializing jar: {}", ref.toString()); - - foundLoader.addNewJar(jarToCache, UpdatePolicy.FORCE); - - } else if (action == DownloadAction.REMOVE_FROM_CACHE) { - JARDesc[] jarToRemove = {new JARDesc(ref, resourceVersion, null, false, true, false, true)}; - foundLoader.removeJars(jarToRemove); - - } else if (action == DownloadAction.CHECK_CACHE) { - return Cache.isAnyCached(ref, resourceVersion); - } - } - return false; - } - - /** - * Decrements loader use count by 1 - *

    - * If count reaches 0, loader is removed from list of available loaders - * - * @throws SecurityException if caller is not trusted - */ - private void decrementLoaderUseCount() { - - // For use by trusted code only - if (System.getSecurityManager() != null) { - System.getSecurityManager().checkPermission(new AllPermission()); - } - - String uniqueKey = file.getUniqueKey(); - - // NB: There will only ever be one class-loader per unique-key - synchronized (getUniqueKeyLock(uniqueKey)) { - useCount--; - - if (useCount <= 0) { - uniqueKeyToLoader.remove(uniqueKey); - } - } - } - - /** - * Returns an appropriate AccessControlContext for loading classes in the - * running instance. - *

    - * The default context during class-loading only allows connection to - * codebase. However applets are allowed to load jars from arbitrary - * locations and the codebase only access falls short if a class from one - * location needs a class from another. - *

    - * Given protected access since CodeBaseClassloader uses this function too. - * - * @return The appropriate AccessControlContext for loading classes for this - * instance - */ - AccessControlContext getAccessControlContextForClassLoading() { - AccessControlContext context = AccessController.getContext(); - - try { - context.checkPermission(new AllPermission()); - return context; // If context already has all permissions, don't bother - } catch (AccessControlException ace) { - // continue below - } - - // Since this is for class-loading, technically any class from one jar - // should be able to access a class from another, therefore making the - // original context code source irrelevant - PermissionCollection permissions = this.security.getSandBoxPermissions(); - - // Local cache access permissions - for (Permission resourcePermission : resourcePermissions) { - permissions.add(resourcePermission); - } - - // Permissions for all remote hosting urls - synchronized (jarLocationSecurityMap) { - for (URL u : jarLocationSecurityMap.keySet()) { - permissions.add(new SocketPermission(UrlUtils.getHostAndPort(u), - "connect, accept")); - } - } - - // Permissions for codebase urls (if there is a loader) - if (codeBaseLoader != null) { - for (URL u : codeBaseLoader.getURLs()) { - permissions.add(new SocketPermission(UrlUtils.getHostAndPort(u), - "connect, accept")); - } - } - - ProtectionDomain pd = new ProtectionDomain(null, permissions); - - return new AccessControlContext(new ProtectionDomain[]{pd}); - } - - public String getMainClass() { - return mainClass; - } - - - public ResourceTracker getTracker() { - return tracker; - } - - public String getMainClassNameFromManifest(JARDesc mainJarDesc) throws IOException { - final File f = tracker.getCacheFile(mainJarDesc.getLocation()); - if (f != null) { - final JarFile mainJar = new JarFile(f); - return mainJar.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); - } - return null; - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/LocateJnlpClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/LocateJnlpClassLoader.java deleted file mode 100644 index 6df31635f..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/LocateJnlpClassLoader.java +++ /dev/null @@ -1,109 +0,0 @@ -/* LocateJNLPClassLoader.java -Copyright (C) 2012, Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.runtime.classloader; - -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; -import net.sourceforge.jnlp.JNLPFile; - -import java.net.URL; - -class LocateJnlpClassLoader { - - /** - * Locates the JNLPClassLoader of the JNLP file. - * @param rootClassLoader Root JNLPClassLoader of the application. - * @param urlToJnlpFile Path of the JNLP file. If {@code null}, main JNLP file's location - * be used instead - * @return the JNLPClassLoader of the JNLP file. - */ - static JNLPClassLoader getLoaderByJnlpFile(final JNLPClassLoader rootClassLoader, URL urlToJnlpFile) { - - if (rootClassLoader == null) - return null; - - JNLPFile file = rootClassLoader.getJNLPFile(); - - if (urlToJnlpFile == null) - urlToJnlpFile = rootClassLoader.getJNLPFile().getFileLocation(); - - if (file.getFileLocation().equals(urlToJnlpFile)) - return rootClassLoader; - - for (JNLPClassLoader loader : rootClassLoader.getLoaders()) { - if (rootClassLoader != loader) { - JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByJnlpFile(loader, urlToJnlpFile); - if (foundLoader != null) - return foundLoader; - } - } - - return null; - } - - /** - * Locates the JNLPClassLoader of the JNLP file's resource. - * @param rootClassLoader Root JNLPClassLoader of the application. - * @param ref Path of the launch or extension JNLP File. If {@code null}, - * main JNLP file's location will be used instead. - * @param version The version of resource. Is null if no version is specified - * @return the JNLPClassLoader of the JNLP file's resource. - */ - static JNLPClassLoader getLoaderByResourceUrl(final JNLPClassLoader rootClassLoader, final URL ref, final String version) { - VersionString resourceVersion = (version == null) ? null : VersionString.fromString(version); - - for (JNLPClassLoader loader : rootClassLoader.getLoaders()) { - ResourcesDesc resources = loader.getJNLPFile().getResources(); - - for (JARDesc eachJar : resources.getJARs()) { - if (ref.equals(eachJar.getLocation()) && - (resourceVersion == null || resourceVersion.equals(eachJar.getVersion()))) - return loader; - } - } - - for (JNLPClassLoader loader : rootClassLoader.getLoaders()) { - if (rootClassLoader != loader) { - JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByResourceUrl(loader, ref, version); - - if (foundLoader != null) - return foundLoader; - } - } - - return null; - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/ManageJnlpResources.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/ManageJnlpResources.java deleted file mode 100644 index 8da1b1036..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/ManageJnlpResources.java +++ /dev/null @@ -1,140 +0,0 @@ -/* ManageJnlpResources.java -Copyright (C) 2012, Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.runtime.classloader; - -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader.DownloadAction; - -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class ManageJnlpResources { - - /** - * Returns jars from the JNLP file with the part name provided. - * @param rootClassLoader Root JNLPClassLoader of the application. - * @param ref Path of the launch or extension JNLP File containing the - * resource. If null, main JNLP's file location will be used instead. - * @param part The name of the part. - * @param version version of jar - * @return jars found. - */ - public static JARDesc[] findJars(final JNLPClassLoader rootClassLoader, final URL ref, final String part, final VersionString version) { - final JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByJnlpFile(rootClassLoader, ref); - - if (foundLoader != null) { - final List foundJars = new ArrayList<>(); - final ResourcesDesc resources = foundLoader.getJNLPFile().getResources(); - - for (final JARDesc aJar : resources.getJARs(part)) { - if (Objects.equals(version, aJar.getVersion())) - foundJars.add(aJar); - } - - return foundJars.toArray(new JARDesc[foundJars.size()]); - } - - return new JARDesc[] {}; - } - - /** - * Removes jars from cache. - * @param classLoader JNLPClassLoader of the application that is associated to the resource. - * @param ref Path of the launch or extension JNLP File containing the - * resource. If null, main JNLP's file location will be used instead. - * @param jars Jars marked for removal. - */ - public static void removeCachedJars(final JNLPClassLoader classLoader, final URL ref, final JARDesc[] jars) { - JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByJnlpFile(classLoader, ref); - - if (foundLoader != null) - foundLoader.removeJars(jars); - } - - /** - * Downloads jars identified by part name. - * @param classLoader JNLPClassLoader of the application that is associated to the resource. - * @param ref Path of the launch or extension JNLP File containing the - * resource. If null, main JNLP's file location will be used instead. - * @param part The name of the path. - * @param version version of jar to be downloaded - */ - public static void downloadJars(final JNLPClassLoader classLoader, final URL ref, final String part, final VersionString version) { - final JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByJnlpFile(classLoader, ref); - - if (foundLoader != null) - foundLoader.initializeNewJarDownload(ref, part, version); - } - - /** - * Downloads and initializes resources which are not mentioned in the jnlp file. - * Used by DownloadService. - * @param rootClassLoader Root JNLPClassLoader of the application. - * @param ref Path to the resource. - * @param version The version of resource. If null, no version is specified. - */ - - public static void loadExternalResourceToCache(final JNLPClassLoader rootClassLoader, final URL ref, final String version) { - rootClassLoader.manageExternalJars(ref, version, DownloadAction.DOWNLOAD_TO_CACHE); - } - - /** - * Removes resource which are not mentioned in the jnlp file. - * Used by DownloadService. - * @param rootClassLoader Root JNLPClassLoader of the application. - * @param ref Path to the resource. - * @param version The version of resource. If null, no version is specified. - */ - public static void removeExternalCachedResource(final JNLPClassLoader rootClassLoader, final URL ref, final String version) { - rootClassLoader.manageExternalJars(ref, version, DownloadAction.REMOVE_FROM_CACHE); - } - - /** - * Returns {@code true} if the resource (not mentioned in the jnlp file) is cached, otherwise {@code false} - * Used by DownloadService. - * @param rootClassLoader Root {@link JNLPClassLoader} of the application. - * @param ref Path to the resource. - * @param version The version of resource. If {@code null}, no version is specified. - * @return {@code true} if the external resource is cached, otherwise {@code false} - */ - public static boolean isExternalResourceCached(final JNLPClassLoader rootClassLoader, final URL ref, final String version) { - return rootClassLoader.manageExternalJars(ref, version, DownloadAction.CHECK_CACHE); - } - -} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegateImpl.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegateImpl.java deleted file mode 100644 index 510376f82..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/SecurityDelegateImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -package net.sourceforge.jnlp.runtime.classloader; - -import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.security.PluginAppVerifier; -import net.sourceforge.jnlp.tools.JarCertVerifier; - -import java.net.URL; -import java.security.Permission; -import java.util.Collection; - -import static net.sourceforge.jnlp.LaunchException.FATAL; - -/** - * Handles security decision logic for the JNLPClassLoader, eg which - * permission level to assign to JARs. - */ -public class SecurityDelegateImpl implements SecurityDelegate { - - private static final Logger LOG = LoggerFactory.getLogger(SecurityDelegateImpl.class); - - private final JNLPClassLoader classLoader; - private boolean runInSandbox; - private boolean promptedForPartialSigning; - - SecurityDelegateImpl(final JNLPClassLoader classLoader) { - this.classLoader = classLoader; - runInSandbox = false; - } - - boolean isPluginApplet() { - return false; - } - - @Override - public SecurityDesc getCodebaseSecurityDesc(final JARDesc jarDesc, final URL codebaseHost) { - if (runInSandbox) { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } else if (isPluginApplet()) { - try { - if (JarCertVerifier.isJarSigned(jarDesc, new PluginAppVerifier(), classLoader.getTracker())) { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.ALL_PERMISSIONS, - codebaseHost); - } else { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } - } catch (final Exception e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } - } else { - return classLoader.getJNLPFile().getSecurity(); - } - } - - @Override - public SecurityDesc getClassLoaderSecurity(final URL codebaseHost) throws LaunchException { - if (isPluginApplet()) { - if (!runInSandbox && classLoader.getSigning()) { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.ALL_PERMISSIONS, - codebaseHost); - } else { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } - } else - /* - * Various combinations of the jars being signed and tags being - * present are possible. They are treated as follows - * - * Jars JNLP File Result - * - * Signed Appropriate Permissions - * Signed no Sandbox - * Unsigned Error - * Unsigned no Sandbox - * - */ - if (!runInSandbox && !classLoader.getSigning() - && !classLoader.getJNLPFile().getSecurity().getSecurityType().equals(SecurityDesc.SANDBOX_PERMISSIONS)) { - if (classLoader.jcv.allJarsSigned()) { - LaunchException ex = new LaunchException(classLoader.getJNLPFile(), null, FATAL, "Application Error", "The JNLP application is not fully signed by a single cert.", "The JNLP application has its components individually signed, however there must be a common signer to all entries."); - JNLPClassLoader.consultCertificateSecurityException(ex); - return consultResult(codebaseHost); - } else { - LaunchException ex = new LaunchException(classLoader.getJNLPFile(), null, FATAL, "Application Error", "Cannot grant permissions to unsigned jars.", "Application requested security permissions, but jars are not signed."); - JNLPClassLoader.consultCertificateSecurityException(ex); - return consultResult(codebaseHost); - } - } else return consultResult(codebaseHost); - } - - private SecurityDesc consultResult(URL codebaseHost) { - if (!runInSandbox && classLoader.getSigning()) { - return classLoader.getJNLPFile().getSecurity(); - } else { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } - } - - @Override - public SecurityDesc getJarPermissions(final URL codebaseHost) { - if (!runInSandbox && classLoader.jcv.isFullySigned()) { - // Already trust application, nested jar should be given - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.ALL_PERMISSIONS, - codebaseHost); - } else { - return new SecurityDesc(classLoader.getJNLPFile(), AppletPermissionLevel.NONE, - SecurityDesc.SANDBOX_PERMISSIONS, - codebaseHost); - } - } - - @Override - public void setRunInSandbox() throws LaunchException { - if (runInSandbox && classLoader.getSecurity() != null - && !classLoader.jarLocationSecurityMap.isEmpty()) { - throw new LaunchException(classLoader.getJNLPFile(), null, FATAL, "Initialization Error", "Run in Sandbox call performed too late.", "The classloader was notified to run the applet sandboxed, but security settings were already initialized."); - } - - JNLPRuntime.reloadPolicy(); - // ensure that we have the most up-to-date custom policy loaded since the user may have just launched PolicyEditor - // to create a custom policy for the applet they are about to run - this.runInSandbox = true; - } - - @Override - public void promptUserOnPartialSigning() throws LaunchException { - if (promptedForPartialSigning) { - return; - } - promptedForPartialSigning = true; - UnsignedAppletTrustConfirmation.checkPartiallySignedWithUserIfRequired(this, classLoader.getJNLPFile(), classLoader.jcv); - } - - @Override - public boolean getRunInSandbox() { - return this.runInSandbox; - } - - void addPermission(final Permission perm) { - classLoader.addPermission(perm); - } - - @Override - public void addPermissions(final Collection perms) { - for (final Permission perm : perms) { - addPermission(perm); - } - } - -} diff --git a/core/src/main/java/net/sourceforge/jnlp/security/AccessType.java b/core/src/main/java/net/sourceforge/jnlp/security/AccessType.java index 61c207f8d..a36b1bc0e 100644 --- a/core/src/main/java/net/sourceforge/jnlp/security/AccessType.java +++ b/core/src/main/java/net/sourceforge/jnlp/security/AccessType.java @@ -8,11 +8,15 @@ public enum AccessType { READ_WRITE_FILE, READ_FILE, WRITE_FILE, - CREATE_DESKTOP_SHORTCUT, CLIPBOARD_READ, CLIPBOARD_WRITE, PRINTER, NETWORK, + + // the following is for creating desktop shortcuts and has nothing to do with access types + CREATE_DESKTOP_SHORTCUT, + + // the following are certificate related states and have nothing to do with access types VERIFIED, UNVERIFIED, PARTIALLY_SIGNED, diff --git a/core/src/main/java/net/sourceforge/jnlp/security/AppVerifier.java b/core/src/main/java/net/sourceforge/jnlp/security/AppVerifier.java deleted file mode 100644 index b18fa41dc..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/security/AppVerifier.java +++ /dev/null @@ -1,91 +0,0 @@ -/* AppVerifier.java - Copyright (C) 2012 Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.security; - -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import net.sourceforge.jnlp.tools.CertInformation; -import net.sourceforge.jnlp.tools.JarCertVerifier; - -import java.security.cert.CertPath; -import java.util.Map; - -/** - * An interface that provides various details about an app's signers. - */ -public interface AppVerifier { - - /** - * Checks if the app has already found trust in its publisher(s). - * - * @param certs The certs to search through and their cert information - * @param signedJars A map of all the jars of this app and the number of - * signed entries each one has. - * @return True if the app trusts its publishers. - */ - boolean hasAlreadyTrustedPublisher(Map certs, Map signedJars); - - /** - * Checks if the app has signer(s) whose certs along their chains are in CA certs. - * - * @param certs The certs to search through and their cert information - * @param signedJars A map of all the jars of this app and the number of - * signed entries each one has. - * @return True if the app has a root in the CA certs store. - */ - boolean hasRootInCacerts(Map certs, Map signedJars); - - /** - * Checks if the app's jars are covered by the provided certificates, enough - * to consider the app fully signed. - * - * @param certs Any possible signer and their respective information regarding this app. - * @param signedJars A map of all the jars of this app and the number of - * signed entries each one has. - * @return true if jar is fully signed - */ - boolean isFullySigned(Map certs, Map signedJars); - - /** - * Prompt the user with requests for trusting the certificates used by this app - * - * @param securityDelegate parental security - * @param jcv jar verifier - * @param file jnlp file to provide information - * @throws LaunchException if it fails to verify - */ - void checkTrustWithUser(SecurityDelegate securityDelegate, JarCertVerifier jcv, JNLPFile file) throws LaunchException; -} diff --git a/core/src/main/java/net/sourceforge/jnlp/security/CertificateUtils.java b/core/src/main/java/net/sourceforge/jnlp/security/CertificateUtils.java index 4cca1e3f1..a08ad0a27 100644 --- a/core/src/main/java/net/sourceforge/jnlp/security/CertificateUtils.java +++ b/core/src/main/java/net/sourceforge/jnlp/security/CertificateUtils.java @@ -34,6 +34,7 @@ package net.sourceforge.jnlp.security; +import net.adoptopenjdk.icedteaweb.io.FileUtils; import net.adoptopenjdk.icedteaweb.io.IOUtils; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; @@ -219,4 +220,20 @@ public static void dumpPKCS12(String alias, File file, KeyStore ks, char[] passw keyStore.setKeyEntry(alias, key, password, certChain); keyStore.store(bos, password); } + + public static void saveCertificate(final X509Certificate certificate) { + File keyStoreFile = null; + try { + KeyStore ks = KeyStores.getKeyStore(KeyStores.Level.USER, KeyStores.Type.CERTS).getKs(); + addToKeyStore(certificate, ks); + keyStoreFile = KeyStores.getKeyStoreLocation(KeyStores.Level.USER, KeyStores.Type.CERTS).getFile(); + if (!keyStoreFile.isFile()) { + FileUtils.createRestrictedFile(keyStoreFile); + } + SecurityUtil.storeKeyStore(ks, keyStoreFile); + LOG.debug("Certificate is now permanently trusted."); + } catch (Exception ex) { + LOG.error(String.format("Error while add certificate '%s' to keystore '%s'.", certificate, keyStoreFile), ex); + } + } } diff --git a/core/src/main/java/net/sourceforge/jnlp/security/HttpsCertVerifier.java b/core/src/main/java/net/sourceforge/jnlp/security/HttpsCertVerifier.java index 59f6c1b17..461ada906 100644 --- a/core/src/main/java/net/sourceforge/jnlp/security/HttpsCertVerifier.java +++ b/core/src/main/java/net/sourceforge/jnlp/security/HttpsCertVerifier.java @@ -67,7 +67,7 @@ public class HttpsCertVerifier implements CertVerifier { private final boolean hostMatched; private final ArrayList details = new ArrayList<>(); - HttpsCertVerifier(final X509Certificate[] chain, + public HttpsCertVerifier(final X509Certificate[] chain, final boolean isTrusted, final boolean hostMatched, final String hostName) { this.chain = chain; diff --git a/core/src/main/java/net/sourceforge/jnlp/security/JNLPAppVerifier.java b/core/src/main/java/net/sourceforge/jnlp/security/JNLPAppVerifier.java deleted file mode 100644 index 833d1043e..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/security/JNLPAppVerifier.java +++ /dev/null @@ -1,145 +0,0 @@ -/* JNLPAppVerifier.java - Copyright (C) 2012 Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.security; - -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import net.sourceforge.jnlp.tools.CertInformation; -import net.sourceforge.jnlp.tools.JarCertVerifier; - -import java.security.cert.CertPath; -import java.util.Map; - -import static net.sourceforge.jnlp.LaunchException.FATAL; - -public class JNLPAppVerifier implements AppVerifier { - - @Override - public boolean hasAlreadyTrustedPublisher( - Map certs, - Map signedJars) { - int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); - for (CertInformation certInfo : certs.values()) { - Map certSignedJars = certInfo.getSignedJars(); - - if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries - && certInfo.isPublisherAlreadyTrusted()) { - return true; - } - } - return false; - } - - @Override - public boolean hasRootInCacerts(Map certs, - Map signedJars) { - int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); - for (CertInformation certInfo : certs.values()) { - Map certSignedJars = certInfo.getSignedJars(); - - if (JarCertVerifier.getTotalJarEntries(certSignedJars) == sumOfSignableEntries - && certInfo.isRootInCacerts()) { - return true; - } - } - return false; - } - - @Override - public boolean isFullySigned(Map certs, - Map signedJars) { - int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(signedJars); - for (CertPath cPath : certs.keySet()) { - // If this cert has signed everything, return true - if (hasCompletelySignedApp(certs.get(cPath), sumOfSignableEntries)) { - return true; - } - } - - // No cert found that signed all entries. Return false. - return false; - } - - @Override - public void checkTrustWithUser(SecurityDelegate securityDelegate, JarCertVerifier jcv, JNLPFile file) - throws LaunchException { - - int sumOfSignableEntries = JarCertVerifier.getTotalJarEntries(jcv.getJarSignableEntries()); - for (CertPath cPath : jcv.getCertsList()) { - jcv.setCurrentlyUsedCertPath(cPath); - CertInformation info = jcv.getCertInformation(cPath); - if (hasCompletelySignedApp(info, sumOfSignableEntries)) { - if (info.isPublisherAlreadyTrusted()) { - return; - } - - AccessType dialogType; - if (info.isRootInCacerts() && !info.hasSigningIssues()) { - dialogType = AccessType.VERIFIED; - } else if (info.isRootInCacerts()) { - dialogType = AccessType.SIGNING_ERROR; - } else { - dialogType = AccessType.UNVERIFIED; - } - - YesNoSandbox action = SecurityDialogs.showCertWarningDialog( - dialogType, file, jcv, securityDelegate); - if (action != null && action.toBoolean()) { - if (action.compareValue(Primitive.SANDBOX)) { - securityDelegate.setRunInSandbox(); - } - return; - } - } - } - - throw new LaunchException(null, null, FATAL, "Launch Error", - "Cancelled on user request.", ""); - } - - /** - * Find out if the CertPath with the given info has fully signed the app. - * @param info The information regarding the CertPath in question - * @param sumOfSignableEntries The total number of signable entries in the app. - * @return True if the signer has fully signed this app. - */ - public boolean hasCompletelySignedApp(CertInformation info, int sumOfSignableEntries) { - return JarCertVerifier.getTotalJarEntries(info.getSignedJars()) == sumOfSignableEntries; - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/security/JNLPAuthenticator.java b/core/src/main/java/net/sourceforge/jnlp/security/JNLPAuthenticator.java index e652beba9..2049d38d4 100644 --- a/core/src/main/java/net/sourceforge/jnlp/security/JNLPAuthenticator.java +++ b/core/src/main/java/net/sourceforge/jnlp/security/JNLPAuthenticator.java @@ -33,7 +33,7 @@ package net.sourceforge.jnlp.security; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.resources.CachedDaemonThreadPoolProvider; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.NamePassword; @@ -65,7 +65,7 @@ public PasswordAuthentication getPasswordAuthentication() { return cached; } - NamePassword response = SecurityDialogs.showAuthenticationPrompt(host, port, prompt, type); + NamePassword response = Dialogs.showAuthenticationPrompt(host, port, prompt, type); if (response == null) { return null; } else { diff --git a/core/src/main/java/net/sourceforge/jnlp/security/PluginAppVerifier.java b/core/src/main/java/net/sourceforge/jnlp/security/PluginAppVerifier.java deleted file mode 100644 index d47cb34c1..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/security/PluginAppVerifier.java +++ /dev/null @@ -1,228 +0,0 @@ -/* PluginAppVerifier.java - Copyright (C) 2012 Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.security; - -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import net.sourceforge.jnlp.tools.CertInformation; -import net.sourceforge.jnlp.tools.JarCertVerifier; - -import java.security.cert.CertPath; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static net.sourceforge.jnlp.LaunchException.FATAL; - -public class PluginAppVerifier implements AppVerifier { - - @Override - public boolean hasAlreadyTrustedPublisher( - Map certs, - Map signedJars) { - - boolean allPublishersTrusted = true; - - for(String jarName : signedJars.keySet()) { - int numbSignableEntries = signedJars.get(jarName); - boolean publisherTrusted = false; - - for (CertInformation certInfo : certs.values()) { - if(certInfo.isSignerOfJar(jarName) - && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName) - && certInfo.isPublisherAlreadyTrusted()) { - publisherTrusted = true; - break; - } - } - - allPublishersTrusted &= publisherTrusted; - } - return allPublishersTrusted; - } - - @Override - public boolean hasRootInCacerts(Map certs, - Map signedJars) { - - boolean allRootCAsTrusted = true; - - for(String jarName : signedJars.keySet()) { - int numbSignableEntries = signedJars.get(jarName); - boolean rootCATrusted = false; - - for (CertInformation certInfo : certs.values()) { - if(certInfo.isSignerOfJar(jarName) - && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName) - && certInfo.isRootInCacerts()) { - rootCATrusted = true; - break; - } - } - - allRootCAsTrusted &= rootCATrusted; - } - return allRootCAsTrusted; - } - - @Override - public boolean isFullySigned(Map certs, - Map signedJars) { - - boolean isFullySigned = true; - - for(String jarName : signedJars.keySet()) { - int numbSignableEntries = signedJars.get(jarName); - boolean isSigned = false; - - for (CertInformation certInfo : certs.values()) { - if(certInfo.isSignerOfJar(jarName) - && numbSignableEntries == certInfo.getNumJarEntriesSigned(jarName)) { - isSigned = true; - break; - } - } - - isFullySigned &= isSigned; - } - - return isFullySigned; - } - - @Override - public void checkTrustWithUser(SecurityDelegate securityDelegate, JarCertVerifier jcv, JNLPFile file) - throws LaunchException { - List certPaths = buildCertPathsList(jcv); - List alreadyApprovedByUser = new ArrayList(); - for (String jarName : jcv.getJarSignableEntries().keySet()) { - boolean trustFoundOrApproved = false; - for (CertPath cPathApproved : alreadyApprovedByUser) { - jcv.setCurrentlyUsedCertPath(cPathApproved); - CertInformation info = jcv.getCertInformation(cPathApproved); - if (info.isSignerOfJar(jarName) - && alreadyApprovedByUser.contains(cPathApproved)) { - trustFoundOrApproved = true; - break; - } - } - - if (trustFoundOrApproved) { - continue; - } - - for (CertPath cPath : certPaths) { - jcv.setCurrentlyUsedCertPath(cPath); - CertInformation info = jcv.getCertInformation(cPath); - if (info.isSignerOfJar(jarName)) { - if (info.isPublisherAlreadyTrusted()) { - trustFoundOrApproved = true; - alreadyApprovedByUser.add(cPath); - break; - } - - AccessType dialogType; - if (info.isRootInCacerts() && !info.hasSigningIssues()) { - dialogType = AccessType.VERIFIED; - } else if (info.isRootInCacerts()) { - dialogType = AccessType.SIGNING_ERROR; - } else { - dialogType = AccessType.UNVERIFIED; - } - - YesNoSandbox action = SecurityDialogs.showCertWarningDialog( - dialogType, file, jcv, securityDelegate); - if (action != null && action.toBoolean()) { - if (action.compareValue(Primitive.SANDBOX)) { - securityDelegate.setRunInSandbox(); - } - alreadyApprovedByUser.add(cPath); - trustFoundOrApproved = true; - break; - } - } - } - if (!trustFoundOrApproved) { - throw new LaunchException(null, null, FATAL, - "Launch Error", "Cancelled on user request.", ""); - } - } - } - - /** - * Build a list of all the CertPaths that were detected in the provided - * JCV, placing them in the most trusted possible order. - * @param jcv The verifier containing the CertPaths to examine. - * @return A list of CertPaths sorted in the following order: Signers with - * 1. Already trusted publishers - * 2. Roots in the CA store and have no signing issues - * 3. Roots in the CA store but have signing issues - * 4. Everything else - */ - public List buildCertPathsList(JarCertVerifier jcv) { - List certPathsList = jcv.getCertsList(); - List returnList = new ArrayList(); - - for (CertPath cPath : certPathsList) { - if (!returnList.contains(cPath) - && jcv.getCertInformation(cPath).isPublisherAlreadyTrusted()) - returnList.add(cPath); - } - - for (CertPath cPath : certPathsList) { - if (!returnList.contains(cPath) - && jcv.getCertInformation(cPath).isRootInCacerts() - && !jcv.getCertInformation(cPath).hasSigningIssues()) - returnList.add(cPath); - } - - for (CertPath cPath : certPathsList) { - if (!returnList.contains(cPath) - && jcv.getCertInformation(cPath).isRootInCacerts() - && jcv.getCertInformation(cPath).hasSigningIssues()) - returnList.add(cPath); - } - - for (CertPath cPath : certPathsList) { - if (!returnList.contains(cPath)) - returnList.add(cPath); - } - - return returnList; - } -} diff --git a/core/src/main/java/net/sourceforge/jnlp/security/VariableX509TrustManager.java b/core/src/main/java/net/sourceforge/jnlp/security/VariableX509TrustManager.java index 61173246a..59d69fc18 100644 --- a/core/src/main/java/net/sourceforge/jnlp/security/VariableX509TrustManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/security/VariableX509TrustManager.java @@ -34,7 +34,7 @@ package net.sourceforge.jnlp.security; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; @@ -394,13 +394,12 @@ private boolean askUser(final X509Certificate[] chain, return AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run() { - YesNoSandbox r = SecurityDialogs.showCertWarningDialog( + YesNoSandbox r = Dialogs.showCertWarningDialog( AccessType.UNVERIFIED, null, new HttpsCertVerifier(chain, isTrusted, hostMatched, hostName), null ); - if (r == null) { return false; } diff --git a/core/src/main/java/net/sourceforge/jnlp/services/PartsCache.java b/core/src/main/java/net/sourceforge/jnlp/services/PartsCache.java new file mode 100644 index 000000000..86c950987 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/services/PartsCache.java @@ -0,0 +1,21 @@ +package net.sourceforge.jnlp.services; + +import net.adoptopenjdk.icedteaweb.classloader.Extension; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; + +import java.net.URL; + +public interface PartsCache { + + void downloadPart(String partName); + + void downloadPart(String partName, Extension extension); + + void downloadPartContainingJar(URL ref, VersionString version); + + boolean isPartDownloaded(String partName); + + boolean isPartDownloaded(String partName, Extension extension); + + boolean isPartContainingJar(URL ref, VersionString version); +} diff --git a/core/src/main/java/net/sourceforge/jnlp/services/ServiceUtil.java b/core/src/main/java/net/sourceforge/jnlp/services/ServiceUtil.java index f03901236..c0540c8c6 100644 --- a/core/src/main/java/net/sourceforge/jnlp/services/ServiceUtil.java +++ b/core/src/main/java/net/sourceforge/jnlp/services/ServiceUtil.java @@ -17,7 +17,7 @@ package net.sourceforge.jnlp.services; import net.adoptopenjdk.icedteaweb.IcedTeaWebConstants; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; @@ -260,7 +260,7 @@ public static boolean checkAccess(ApplicationInstance app, AccessType type, return false; } if (app == null) { - app = JNLPRuntime.getApplication(); + app = JNLPRuntime.getApplication().orElseThrow(() -> new RuntimeException("Could not determine application")); } final AccessType tmpType = type; @@ -273,7 +273,7 @@ public static boolean checkAccess(ApplicationInstance app, AccessType type, Boolean b = AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run() { - AccessWarningPaneComplexReturn r = SecurityDialogs.showAccessWarningDialog(tmpType, + AccessWarningPaneComplexReturn r = Dialogs.showAccessWarningDialog(tmpType, tmpApp.getJNLPFile(), tmpExtras); if (r == null) { return false; @@ -316,9 +316,6 @@ public Boolean run() { public static boolean isSigned(ApplicationInstance app) { - if (app == null) { - app = JNLPRuntime.getApplication(); - } StackTraceElement[] stack = Thread.currentThread().getStackTrace(); @@ -329,6 +326,9 @@ public static boolean isSigned(ApplicationInstance app) { } catch (Exception e1) { LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e1); try { + if (app == null) { + app = JNLPRuntime.getApplication().orElseThrow(() -> new RuntimeException("Could not determine application")); + } c = Class.forName(stack1.getClassName(), false, app.getClassLoader()); }catch (Exception e2) { LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e2); diff --git a/core/src/main/java/net/sourceforge/jnlp/services/XBasicService.java b/core/src/main/java/net/sourceforge/jnlp/services/XBasicService.java index 585381c9f..73dbfdea4 100644 --- a/core/src/main/java/net/sourceforge/jnlp/services/XBasicService.java +++ b/core/src/main/java/net/sourceforge/jnlp/services/XBasicService.java @@ -1,4 +1,5 @@ // Copyright (C) 2001 Jon A. Maxwell (JAM) +// Copyright (C) 2020 Karakun AG // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -51,17 +52,18 @@ import java.awt.event.WindowListener; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.util.Optional; import java.util.StringTokenizer; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; /** - * The BasicService JNLP service. + * The {@link BasicService} service provides a set of methods for querying and interacting with the environment. * - * @author Jon A. Maxwell - * (JAM) - initial author - * @version $Revision: 1.10 $ + * @implSpec See JSR-56, Section 7.1 The BasicService Service for a details. */ class XBasicService implements BasicService { @@ -71,42 +73,36 @@ class XBasicService implements BasicService { } /** - * Returns the codebase of the application, applet, or installer. If the - * codebase was not specified in the JNLP element then the main JAR's - * location is returned. If no main JAR was specified then the location of - * the JAR containing the main class is returned. + * @return the codebase for the application. This will typically be the URL specified + * in the codebase attribute in the jnlp element. However, if the JNLP file does not specify this attribute, + * then the codebase is defined to be the URL of the JAR file containing the class with the main method. */ @Override public URL getCodeBase() { - final ApplicationInstance app = JNLPRuntime.getApplication(); + final Optional app = JNLPRuntime.getApplication(); - if (app != null) { - final JNLPFile file = app.getJNLPFile(); + if (app.isPresent()) { + final JNLPFile file = app.get().getJNLPFile(); - // return the codebase. if (file.getCodeBase() != null) { return file.getCodeBase(); } - // else return the main JAR's URL. final JARDesc mainJar = file.getResources().getMainJAR(); if (mainJar != null) { return mainJar.getLocation(); } - - // else find JAR where main class was defined. - // - // JNLPFile file = app.getJNLPFile(); - // String mainClass = file.getEntryPointDesc().getMainClass()+".class"; - // URL jarUrl = app.getClassLoader().getResource(mainClass); - // go through list of JARDesc to find one matching jarUrl } + LOG.warn("Could not find application instance."); return null; } /** - * Return true if the Environment is Offline + * @return true if the application is running without access to the network. An application can use this + * method to adjust its behavior to work properly in an offline environment. The method provides a hint + * from the JNLP Client. The network might be unavailable, even though the JNLP Client indicated that it + * was, and vice-versa. */ @Override public boolean isOffline() { @@ -121,10 +117,10 @@ public boolean isOffline() { */ private URL findFirstURLFromJNLPFile() { - final ApplicationInstance app = JNLPRuntime.getApplication(); + final Optional app = JNLPRuntime.getApplication(); - if (app != null) { - final JNLPFile jnlpFile = app.getJNLPFile(); + if (app.isPresent()) { + final JNLPFile jnlpFile = app.get().getJNLPFile(); final URL sourceURL = jnlpFile.getSourceLocation(); if (sourceURL != null) { @@ -167,55 +163,57 @@ public boolean isWebBrowserSupported() { } /** - * Show a document. + * Displays the given URL in a Web browser. This may be the default browser on the platform, or it may be + * chosen by the JNLP Client some other way. This method returns false if the request failed, or the + * operation is not supported. * - * @return whether the document was opened + * @param url giving the location of the document. A relative URL will be relative to the codebase. + * @return true if the request succeeded, false if the url is null or the request failed. */ @Override public boolean showDocument(final URL url) { - try { -// if (url.toString().endsWith(".jnlp")) { -// try { -// new Launcher(false).launchExternal(url); -// return true; -// } catch (Exception ex) { -// return false; -// } -// } -// Ignorance of this code is the only regression against original code (if you assume most of the jnlps have jnlp suffix...) we had -// anyway, also jnlp protocol should be handled via this, so while this can be set via -// ALWAYS-ASK, or directly via BROWSER of deployment.browser.path , it still should be better then it was -// in all cases, the mime recognition is much harder then .jnlp suffix - - final String urls = url.toExternalForm(); - LOG.debug("showDocument for: {}", urls); - - final DeploymentConfiguration config = JNLPRuntime.getConfiguration(); - final String command = config.getProperty(ConfigurationConstants.KEY_BROWSER_PATH); - //for various debugging - //command=DeploymentConfiguration.ALWAYS_ASK; - if (command != null) { - LOG.debug("{} located. Using: {}", ConfigurationConstants.KEY_BROWSER_PATH, command); - return exec(command, urls); - } - if (System.getenv(ConfigurationConstants.BROWSER_ENV_VAR) != null) { - final String cmd = System.getenv(ConfigurationConstants.BROWSER_ENV_VAR); - LOG.debug("variable {} located. Using: {}", ConfigurationConstants.BROWSER_ENV_VAR, command); - return exec(cmd, urls); - } + if (url == null) { + return false; + } - if (JNLPRuntime.isHeadless() || !Desktop.isDesktopSupported()) { - final String cmd = promptForCommand(urls, false); - return exec(cmd, urls); - } else { - LOG.debug("using default browser"); - Desktop.getDesktop().browse(url.toURI()); + final String urlString = url.toExternalForm(); + LOG.debug("About to display '{}' in a Web browser", urlString); + + final DeploymentConfiguration config = JNLPRuntime.getConfiguration(); + final String command = config.getProperty(ConfigurationConstants.KEY_BROWSER_PATH); + if (command != null) { + LOG.debug("Browser path configuration property '{} = {}' detected.", ConfigurationConstants.KEY_BROWSER_PATH, command); + return exec(command, urlString); + } + if (System.getenv(ConfigurationConstants.BROWSER_ENV_VAR) != null) { + final String cmd = System.getenv(ConfigurationConstants.BROWSER_ENV_VAR); + LOG.debug("Browser environment variable '{} = {}' detected.", ConfigurationConstants.BROWSER_ENV_VAR, cmd); + return exec(cmd, urlString); + } + + if (JNLPRuntime.isHeadless() || !Desktop.isDesktopSupported()) { + final String cmd; + try { + cmd = promptForCommand(urlString, false); + return exec(cmd, urlString); + } catch (IOException e) { + LOG.error("Could not display '{}' in a Web browser as prompt for command failed", url); + } + } else { + try { + final URI uri = url.toURI(); + LOG.debug("Using default browser to show {}", uri.toString()); + Desktop.getDesktop().browse(uri); return true; + } catch (URISyntaxException e) { + e.printStackTrace(); + LOG.error("Could not display '{}' in a Web browser as it is not a valid URI reference", url); + } catch (IOException e) { + e.printStackTrace(); + LOG.error("Could not display '{}' in a Web browser as the default browser is not found", url); } - } catch (Exception e) { - LOG.error(IcedTeaWebConstants.DEFAULT_ERROR_MESSAGE, e); - return false; } + return false; } //cmd form user can contains spaces, quotes and so... now we are relying on default dummy impl diff --git a/core/src/main/java/net/sourceforge/jnlp/services/XDownloadService.java b/core/src/main/java/net/sourceforge/jnlp/services/XDownloadService.java index b4c7d3c61..f5e31ccf4 100644 --- a/core/src/main/java/net/sourceforge/jnlp/services/XDownloadService.java +++ b/core/src/main/java/net/sourceforge/jnlp/services/XDownloadService.java @@ -16,37 +16,31 @@ package net.sourceforge.jnlp.services; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.classloader.Extension; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; import net.adoptopenjdk.icedteaweb.resources.cache.Cache; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; +import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.runtime.classloader.ManageJnlpResources; +import net.sourceforge.jnlp.util.UrlUtils; import javax.jnlp.DownloadService; import javax.jnlp.DownloadServiceListener; -import java.io.IOException; import java.net.URL; +import java.util.Arrays; + +import static net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment.ALL; /** - * The DownloadService JNLP service. + * The {@link DownloadService} service allows an application to control how its own resources are cached. * - * @author Jon A. Maxwell (JAM) - initial author - * @version $Revision: 1.7 $ + * @implSpec See JSR-56, Section 7.2 The DownloadService Service for a details. */ class XDownloadService implements DownloadService { - /** - * Returns the {@link JNLPClassLoader} of the application - * @return the {@link JNLPClassLoader} of the application - */ - JNLPClassLoader getClassLoader() { - return (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); - } - /** * Returns a listener that will automatically display download * progress to the user. + * * @return always {@code null} */ public DownloadServiceListener getDefaultProgressWindow() { @@ -54,201 +48,160 @@ public DownloadServiceListener getDefaultProgressWindow() { } /** - * Returns whether the part in an extension (specified by the - * url and version) is cached locally. + * {@inheritDoc} */ @Override public boolean isExtensionPartCached(final URL ref, final String version, final String part) { - boolean allCached = true; - final VersionString resourceVersion = (version == null) ? null : VersionString.fromString(version); - - final JARDesc[] jars = ManageJnlpResources.findJars(this.getClassLoader(), ref, part, resourceVersion); - - if (jars.length <= 0) - return false; - - for (int i = 0; i < jars.length && allCached; i++) { - allCached = Cache.isAnyCached(jars[i].getLocation(), resourceVersion); - } - - return allCached; + return getApplication().getPartsCache().isPartDownloaded(part, new Extension(ref, version)); } /** - * Returns whether the parts in an extension (specified by the - * url and version) are cached locally. + * {@inheritDoc} */ @Override public boolean isExtensionPartCached(final URL ref, final String version, final String[] parts) { - boolean allCached = true; - if (parts.length <= 0) - return false; - - for (String eachPart : parts) - allCached = this.isExtensionPartCached(ref, version, eachPart); - - return allCached; + return Arrays.stream(parts).allMatch(part -> isExtensionPartCached(ref, version, part)); } /** - * Returns whether the part of the calling application is cached - * locally. If called by code specified by an extension - * descriptor, the specified part refers to the extension not - * the application. + * {@inheritDoc} */ @Override public boolean isPartCached(final String part) { - boolean allCached = true; - final JARDesc[] jars = ManageJnlpResources.findJars(this.getClassLoader(), null, part, null); - - if (jars.length <= 0) - return false; - - for (int i = 0; i < jars.length && allCached; i++) { - allCached = Cache.isAnyCached(jars[i].getLocation(), jars[i].getVersion()); - } - - return allCached; + return getApplication().getPartsCache().isPartDownloaded(part); } /** - * Returns whether all of the parts of the calling application - * are cached locally. If called by code in an extension, the - * part refers the the part of the extension not the - * application. + * {@inheritDoc} */ @Override public boolean isPartCached(final String[] parts) { - boolean allCached = true; - if (parts.length <= 0) - return false; - - for (final String eachPart : parts) - allCached = this.isPartCached(eachPart); - - return allCached; + return Arrays.stream(parts).allMatch(this::isPartCached); } /** - * Returns whether the resource is cached locally. This method - * only returns true if the resource is specified by the calling - * application or extension. + * {@inheritDoc} */ @Override public boolean isResourceCached(final URL ref, final String version) { - return ManageJnlpResources.isExternalResourceCached(this.getClassLoader(), ref, version); + final VersionString resourceVersion = getVersionString(version); + return Cache.isAnyCached(ref, resourceVersion) && resourceFitToJnlpDetails(ref, resourceVersion); } /** - * Downloads the parts of an extension. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void loadExtensionPart(final URL ref, final String version, final String[] parts, final DownloadServiceListener progress) throws IOException { - for (final String eachPart : parts) - this.loadExtensionPart(ref, version, eachPart, progress); + public void loadExtensionPart(final URL ref, final String version, final String[] parts, final DownloadServiceListener progress) { + for (String part : parts) { + this.loadExtensionPart(ref, version, part, progress); + } } /** - * Downloads a part of an extension. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void loadExtensionPart(final URL ref, final String version, final String part, final DownloadServiceListener progress) throws IOException { - final VersionString resourceVersion = (version == null) ? null : VersionString.fromString(version); - ManageJnlpResources.downloadJars(this.getClassLoader(), ref, part, resourceVersion); + public void loadExtensionPart(final URL ref, final String version, final String part, final DownloadServiceListener progress) { + getApplication().getPartsCache().downloadPart(part, new Extension(ref, version)); } /** - * Downloads the parts. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void loadPart(final String[] parts, final DownloadServiceListener progress) throws IOException { - for (String eachPart : parts) + public void loadPart(final String[] parts, final DownloadServiceListener progress) { + for (String eachPart : parts) { this.loadPart(eachPart, progress); + } } /** - * Downloads the part. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void loadPart(final String part, final DownloadServiceListener progress) throws IOException { - ManageJnlpResources.downloadJars(this.getClassLoader(), null, part, null); + public void loadPart(final String part, final DownloadServiceListener progress) { + getApplication().getPartsCache().downloadPart(part); } /** - * Downloads a resource. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void loadResource(final URL ref, final String version, final DownloadServiceListener progress) throws IOException { - ManageJnlpResources.loadExternalResourceToCache(this.getClassLoader(), ref, version); + public void loadResource(final URL ref, final String version, final DownloadServiceListener progress) { + final VersionString resourceVersion = getVersionString(version); + if (resourceFitToJnlpDetails(ref, resourceVersion)) { + getApplication().getPartsCache().downloadPartContainingJar(ref, resourceVersion); + } } /** - * Notify the system that an extension's part is no longer - * important to cache. - * - * @throws IOException + * @return true if the resource is either mentioned in the calling applications JNLP file, or + * is within the codebase of the calling applications JNLP file, or + * if the calling application has been granted all-permissions. + */ + private boolean resourceFitToJnlpDetails(final URL ref, final VersionString version) { + boolean isResourceAllowedToBeProcessed = getApplication().getApplicationEnvironment() == ALL; + + if (!isResourceAllowedToBeProcessed) { + isResourceAllowedToBeProcessed = getApplication().getPartsCache().isPartContainingJar(ref, version); + } + + if (!isResourceAllowedToBeProcessed) { + final URL codeBase = getApplication().getJNLPFile().getCodeBase(); + isResourceAllowedToBeProcessed = UrlUtils.urlRelativeTo(ref, codeBase); + } + + return isResourceAllowedToBeProcessed; + } + + + /** + * {@inheritDoc} */ @Override - public void removeExtensionPart(final URL ref, final String version, final String part) throws IOException { - final VersionString resourceVersion = (version == null) ? null : VersionString.fromString(version); - final JARDesc[] jars = ManageJnlpResources.findJars(this.getClassLoader(), ref, part, resourceVersion); - ManageJnlpResources.removeCachedJars(this.getClassLoader(), ref, jars); + public void removeExtensionPart(final URL ref, final String version, final String part) { + throw new RuntimeException("Currently not supported"); } /** - * Notify the system that an extension's parts are no longer - * important to cache. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void removeExtensionPart(final URL ref, final String version, final String[] parts) throws IOException { - for (String eachPart : parts) - this.removeExtensionPart(ref, version, eachPart); + public void removeExtensionPart(final URL ref, final String version, final String[] parts) { + throw new RuntimeException("Currently not supported"); } /** - * Notifies the system that a part is no longer important to - * cache. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void removePart(final String part) throws IOException { - final JARDesc[] jars = ManageJnlpResources.findJars(this.getClassLoader(), null, part, null); - ManageJnlpResources.removeCachedJars(this.getClassLoader(), null, jars); + public void removePart(final String part) { + throw new RuntimeException("Currently not supported"); } /** - * Notifies the system that the parts is no longer important to - * cache. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void removePart(final String[] parts) throws IOException { - for (String eachPart : parts) - this.removePart(eachPart); + public void removePart(final String[] parts) { + throw new RuntimeException("Currently not supported"); } /** - * Notifies the system that the resource is no longer important - * to cache. - * - * @throws IOException + * {@inheritDoc} */ @Override - public void removeResource(final URL ref, final String version) throws IOException { - ManageJnlpResources.removeExternalCachedResource(this.getClassLoader(), ref, version); + public void removeResource(final URL ref, final String version) { + throw new RuntimeException("Currently not supported"); + } + + private ApplicationInstance getApplication() { + return JNLPRuntime.getApplication() + .orElseThrow(() -> new IllegalStateException("Could not find application.")); + } + + private static VersionString getVersionString(String version) { + return (version == null) ? null : VersionString.fromString(version); } } diff --git a/core/src/main/java/net/sourceforge/jnlp/services/XPersistenceService.java b/core/src/main/java/net/sourceforge/jnlp/services/XPersistenceService.java index 1bcf22de3..25befa488 100644 --- a/core/src/main/java/net/sourceforge/jnlp/services/XPersistenceService.java +++ b/core/src/main/java/net/sourceforge/jnlp/services/XPersistenceService.java @@ -57,9 +57,7 @@ class XPersistenceService implements PersistenceService { * @throws MalformedURLException if the application cannot access the location */ private void checkLocation(URL location) throws MalformedURLException { - ApplicationInstance app = JNLPRuntime.getApplication(); - if (app == null) - throw new MalformedURLException("Cannot determine the current application."); + ApplicationInstance app = JNLPRuntime.getApplication().orElseThrow(() -> new MalformedURLException("Cannot determine the current application.")); URL source = app.getJNLPFile().getCodeBase(); diff --git a/core/src/main/java/net/sourceforge/jnlp/services/XSingleInstanceService.java b/core/src/main/java/net/sourceforge/jnlp/services/XSingleInstanceService.java index b30fa387b..db424787c 100644 --- a/core/src/main/java/net/sourceforge/jnlp/services/XSingleInstanceService.java +++ b/core/src/main/java/net/sourceforge/jnlp/services/XSingleInstanceService.java @@ -96,7 +96,7 @@ public void run() { public void initializeSingleInstance() { // this is called after the application has started. so safe to use // JNLPRuntime.getApplication() - final JNLPFile jnlpFile = JNLPRuntime.getApplication().getJNLPFile(); + final JNLPFile jnlpFile = JNLPRuntime.getApplication().orElseThrow(() -> new RuntimeException("could not get application")).getJNLPFile(); if (!initialized) { // Either a new process or a new applet being handled by the plugin. checkSingleInstanceRunning(jnlpFile); diff --git a/core/src/main/java/net/sourceforge/jnlp/signing/ApplicationSigningState.java b/core/src/main/java/net/sourceforge/jnlp/signing/ApplicationSigningState.java new file mode 100644 index 000000000..f0bfffa05 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/signing/ApplicationSigningState.java @@ -0,0 +1,7 @@ +package net.sourceforge.jnlp.signing; + +enum ApplicationSigningState { + FULL, // all jars are signed + PARTIAL, // some jars are signed + NONE // no jars are signed +} diff --git a/core/src/main/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJar.java b/core/src/main/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJar.java new file mode 100644 index 000000000..0ff3835b3 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJar.java @@ -0,0 +1,45 @@ +package net.sourceforge.jnlp.signing; + +import net.adoptopenjdk.icedteaweb.Assert; + +import java.io.File; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Holds the set of all {@link CertPath} which fully sign a single jar file. + */ +public class CertificatesFullySigningTheJar { + + private final File jarFile; + private final Set fullySigningCertificates; + + public CertificatesFullySigningTheJar(File jarFile, Set fullySigningCertificates) { + this.jarFile = jarFile; + this.fullySigningCertificates = fullySigningCertificates; + } + + public Set getCertificates() { + Set calculated = getCertificatePaths().stream() + .map(certPath -> certPath.getCertificates().get(0)) + .collect(Collectors.toSet()); + return Collections.unmodifiableSet(calculated); + } + + public Set getCertificatePaths() { + return Collections.unmodifiableSet(fullySigningCertificates); + } + + public boolean contains(final CertPath certPath) { + Assert.requireNonNull(certPath, "certPath"); + return fullySigningCertificates.contains(certPath); + } + + public boolean contains(final Certificate certificate) { + Assert.requireNonNull(certificate, "certificate"); + return getCertificates().contains(certificate); + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/tools/JarCertVerifier.java b/core/src/main/java/net/sourceforge/jnlp/signing/JarCertVerifier.java similarity index 68% rename from core/src/main/java/net/sourceforge/jnlp/tools/JarCertVerifier.java rename to core/src/main/java/net/sourceforge/jnlp/signing/JarCertVerifier.java index 1bc7624d6..93f8df9ec 100644 --- a/core/src/main/java/net/sourceforge/jnlp/tools/JarCertVerifier.java +++ b/core/src/main/java/net/sourceforge/jnlp/signing/JarCertVerifier.java @@ -23,19 +23,23 @@ * have any questions. */ -package net.sourceforge.jnlp.tools; +package net.sourceforge.jnlp.signing; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.Primitive; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import net.sourceforge.jnlp.security.AppVerifier; +import net.sourceforge.jnlp.runtime.SecurityDelegate; +import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; import net.sourceforge.jnlp.security.CertificateUtils; import net.sourceforge.jnlp.security.KeyStores; +import net.sourceforge.jnlp.tools.CertInformation; import net.sourceforge.jnlp.util.JarFile; import sun.security.util.DerInputStream; import sun.security.util.DerValue; @@ -60,10 +64,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.jar.JarEntry; -import java.util.regex.Pattern; +import java.util.stream.Collectors; import static java.time.temporal.ChronoUnit.MONTHS; +import static net.sourceforge.jnlp.LaunchException.FATAL; /** * The jar certificate verifier utility. @@ -72,17 +78,11 @@ * @author Jan Luehe */ +@Deprecated public class JarCertVerifier implements CertVerifier { private static final Logger LOG = LoggerFactory.getLogger(JarCertVerifier.class); - private static final String META_INF = "META-INF/"; - private static final Pattern SIG = Pattern.compile(".*" + META_INF + "SIG-.*"); - - enum VerifyResult { - UNSIGNED, SIGNED_OK, SIGNED_NOT_OK - } - /** * All of the jar files that were verified for signing */ @@ -103,43 +103,96 @@ enum VerifyResult { */ private final Map jarSignableEntries = new HashMap<>(); - /** - * The application verifier to use by this instance - */ - private final AppVerifier appVerifier; - /** * Temporary cert path hack to be used to keep track of which one a UI dialog is using */ private CertPath currentlyUsed; + + + + + + + + + + + + private ApplicationSigningState getState(final Certificate certificate) { + final List allResources = getAllResources(); + + final long numFullySignedResources = allResources.stream() + .filter(certificatesFullySigningTheJar -> certificatesFullySigningTheJar.contains(certificate)) + .count(); + + if (numFullySignedResources == allResources.size()) { + return ApplicationSigningState.FULL; + } + + return numFullySignedResources == 0 ? ApplicationSigningState.NONE : ApplicationSigningState.PARTIAL; + } + + public ApplicationSigningState getState() { + final List allResources = getAllResources(); + + final Set certificates = allResources.stream() + .flatMap(r -> r.getCertificates().stream()) + .collect(Collectors.toSet()); + + return certificates.stream() + .map(this::getState) + .reduce(this::mergeSigningState) + .orElse(ApplicationSigningState.NONE); // What is the correct state if we do not have any certificates???? + } + + private ApplicationSigningState mergeSigningState(final ApplicationSigningState state1, final ApplicationSigningState state2) { + if (state1 == ApplicationSigningState.FULL && state2 == ApplicationSigningState.FULL) { + return ApplicationSigningState.FULL; + } + if (state1 == ApplicationSigningState.NONE && state2 == ApplicationSigningState.NONE) { + return ApplicationSigningState.NONE; + } + return ApplicationSigningState.PARTIAL; + } + + public List getAllResources() { + return null; + } + + + + + + + + + + + /** - * Create a new jar certificate verifier utility that uses the provided verifier for its strategy pattern. + * Returns if all jars are signed. * - * @param verifier The application verifier to be used by the new instance. + * @return True if all jars are signed, false if there are one or more unsigned jars */ - public JarCertVerifier(AppVerifier verifier) { - appVerifier = verifier; + public boolean allJarsSigned() { + return unverifiedJars.isEmpty(); } - /** - * @return true if there are no signable entries in the jar. - * This will return false if any of verified jars have content more than just META-INF/. - */ - public boolean isTriviallySigned() { - return getTotalJarEntries(jarSignableEntries) <= 0 && certs.size() <= 0; + public void checkTrustWithUser(final SecurityDelegate securityDelegate, final JNLPFile file) throws LaunchException { + checkTrustWithUser(securityDelegate, this, file); } @Override public boolean getAlreadyTrustPublisher() { - final boolean allPublishersTrusted = appVerifier.hasAlreadyTrustedPublisher(certs, jarSignableEntries); + final boolean allPublishersTrusted = hasAlreadyTrustedPublisher(certs, jarSignableEntries); LOG.debug("App already has trusted publisher: {}", allPublishersTrusted); return allPublishersTrusted; } @Override public boolean getRootInCaCerts() { - final boolean allRootCAsTrusted = appVerifier.hasRootInCacerts(certs, jarSignableEntries); + final boolean allRootCAsTrusted = hasRootInCacerts(certs, jarSignableEntries); LOG.debug("App has trusted root CA: {}", allRootCAsTrusted); return allRootCAsTrusted; } @@ -154,16 +207,43 @@ public List getDetails(final CertPath certPath) { if (certPath != null) { currentlyUsed = certPath; } - return certs.get(currentlyUsed).getDetailsAsStrings(); + return Optional.ofNullable(certs.get(currentlyUsed)) + .map(ci -> ci.getDetailsAsStrings()) + .orElse(Collections.emptyList()); } - /** - * Get a list of the cert paths of all signers across the app. - * - * @return List of CertPath vars representing each of the signers present on any jar. - */ - public List getCertsList() { - return new ArrayList<>(certs.keySet()); + @Override + public Certificate getPublisher(final CertPath certPath) { + if (certPath != null) { + currentlyUsed = certPath; + } + if (currentlyUsed != null) { + final List certList = currentlyUsed.getCertificates(); + if (certList.size() > 0) { + return certList.get(0); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public Certificate getRoot(final CertPath certPath) { + if (certPath != null) { + currentlyUsed = certPath; + } + if (currentlyUsed != null) { + final List certList = currentlyUsed.getCertificates(); + if (certList.size() > 0) { + return certList.get(certList.size() - 1); + } else { + return null; + } + } else { + return null; + } } /** @@ -185,23 +265,10 @@ public CertInformation getCertInformation(final CertPath cPath) { * * @return Whether or not the app is considered signed. */ - // FIXME: Change javadoc once applets do not need entire jars signed. public boolean isFullySigned() { return isTriviallySigned() || isSigned(); } - private boolean isSigned() { - final boolean fullySigned = appVerifier.isFullySigned(certs, jarSignableEntries); - LOG.debug("App already has trusted publisher: {}", fullySigned); - return fullySigned; - } - - public static boolean isJarSigned(final JARDesc jar, final AppVerifier verifier, final ResourceTracker tracker) throws Exception { - final JarCertVerifier certVerifier = new JarCertVerifier(verifier); - certVerifier.add(jar, tracker); - return certVerifier.allJarsSigned(); - } - /** * Update the verifier to consider a new jar when verifying. * @@ -213,84 +280,14 @@ public void add(final JARDesc jar, final ResourceTracker tracker) throws Excepti verifyJars(Collections.singletonList(jar), tracker); } - /** - * Update the verifier to consider new jars when verifying. - * - * @param jars List of new jars to be verified. - * @param tracker Resource tracker used to obtain the the jars from cache - * @throws Exception Caused by issues with obtaining the jars' entries or interacting with the tracker. - */ - public void add(final List jars, final ResourceTracker tracker) throws Exception { - verifyJars(jars, tracker); - } /** - * Verify the jars provided and update the state of this instance to match the new information. - * - * @param jars List of new jars to be verified. - * @param tracker Resource tracker used to obtain the the jars from cache - * @throws Exception Caused by issues with obtaining the jars' entries or interacting with the tracker. - */ - private void verifyJars(final List jars, final ResourceTracker tracker) throws Exception { - - for (JARDesc jar : jars) { - final File jarFile = tracker.getCacheFile(jar.getLocation()); - - // some sort of resource download/cache error. Nothing to add - // in that case ... but don't fail here - if (jarFile == null || !jarFile.isFile()) { - continue; - } - - final String jarPath = jarFile.getCanonicalFile().getAbsolutePath(); - if (verifiedJars.contains(jarPath) || unverifiedJars.contains(jarPath)) { - continue; - } - - final VerifyResult result = verifyJar(jarPath); - if (result == VerifyResult.UNSIGNED) { - unverifiedJars.add(jarPath); - } else if (result == VerifyResult.SIGNED_NOT_OK) { - verifiedJars.add(jarPath); - } else if (result == VerifyResult.SIGNED_OK) { - verifiedJars.add(jarPath); - } - } - - for (CertPath certPath : certs.keySet()) { - checkTrustedCerts(certPath); - } - } - - /** - * Checks through all the jar entries of jarName for signers, storing all the common ones in the certs hash map. + * Get a list of the cert paths of all signers across the app. * - * @param jarPath The absolute path to the jar file. - * @return The return of {@link JarCertVerifier#verifyJarEntryCerts} using the entries found in the jar located at jarName. + * @return List of CertPath vars representing each of the signers present on any jar. */ - private VerifyResult verifyJar(final String jarPath) { - try (final JarFile jarFile = new JarFile(jarPath, true)) { - final List entries = new ArrayList<>(); - final byte[] buffer = new byte[8192]; - - final Enumeration entriesEnum = jarFile.entries(); - while (entriesEnum.hasMoreElements()) { - final JarEntry entry = entriesEnum.nextElement(); - entries.add(entry); - - try (InputStream is = jarFile.getInputStream(entry)) { - //noinspection StatementWithEmptyBody - while (is.read(buffer, 0, buffer.length) != -1) { - // we just read. this will throw a SecurityException - // if a signature/digest check fails. - } - } - } - return verifyJarEntryCerts(jarPath, jarFile.getManifest() != null, entries); - } catch (Exception e) { - LOG.error("Error in verify jar " + jarPath, e); - throw new RuntimeException("Error in verify jar " + jarPath, e); - } + List getCertsList() { + return new ArrayList<>(certs.keySet()); } /** @@ -302,7 +299,7 @@ private VerifyResult verifyJar(final String jarPath) { * @return If there is at least one signable entry that is not signed by a common signer, return UNSIGNED. Otherwise every signable entry is signed by at least one common signer. If the signer has no issues, return SIGNED_OK. If there are any signing issues, return SIGNED_NOT_OK. * @throws RuntimeException Will be thrown if there are issues with entries. */ - VerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManifest, final List entries) { + SignVerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManifest, final List entries) { // Contains number of entries the cert with this CertPath has signed. final Map jarSignCount = new HashMap<>(); final Map codeSigners = new HashMap<>(); @@ -313,7 +310,7 @@ VerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManif final ZonedDateTime expiresSoon = now.plus(6, MONTHS); if (jarHasManifest) { for (JarEntry je : entries) { - final boolean shouldHaveSignature = !je.isDirectory() && !isMetaInfFile(je.getName()); + final boolean shouldHaveSignature = !je.isDirectory() && !SignVerifyUtils.isMetaInfFile(je.getName()); if (shouldHaveSignature) { numSignableEntriesInJar++; final CodeSigner[] signers = je.getCodeSigners(); @@ -351,10 +348,9 @@ VerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManif final ZonedDateTime notBefore = zonedDateTime(((X509Certificate) cert).getNotBefore()); final ZonedDateTime notAfter = zonedDateTime(((X509Certificate) cert).getNotAfter()); - final Optional optionalTsa = Optional.ofNullable(codeSigners.get(certPath)) + final Optional optionalSignatureTimestamp = Optional.ofNullable(codeSigners.get(certPath)) .map(CodeSigner::getTimestamp); - - final X509Certificate tsaCertificate = (X509Certificate) optionalTsa + final X509Certificate tsaCertificate = (X509Certificate) optionalSignatureTimestamp .map(Timestamp::getSignerCertPath) .map(CertPath::getCertificates) .filter(certs -> !certs.isEmpty()) @@ -362,18 +358,12 @@ VerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManif .filter(c -> c instanceof X509Certificate) .orElse(null); - final CertPath tsaCertPath = optionalTsa - .map(Timestamp::getSignerCertPath) - .orElse(null); - - final Date tsaDate = optionalTsa - .map(Timestamp::getTimestamp) - .orElse(null); - - if (tsaCertificate != null && isTrustedTsa(tsaCertPath)) { + if (tsaCertificate != null) { final ZonedDateTime tsaNotBefore = zonedDateTime(tsaCertificate.getNotBefore()); final ZonedDateTime tsaNotAfter = zonedDateTime(tsaCertificate.getNotAfter()); - final ZonedDateTime signedAt = zonedDateTime(tsaDate); + final ZonedDateTime signedAt = zonedDateTime(optionalSignatureTimestamp + .orElseThrow(() -> new RuntimeException("timestamp is null even tough we have a tsa certificate")) + .getTimestamp()); if (signedAt.isBefore(tsaNotBefore) || now.isBefore(tsaNotBefore)) { certInfo.setNotYetValidCert(); @@ -406,53 +396,117 @@ VerifyResult verifyJarEntryCerts(final String jarPath, final boolean jarHasManif // Every signable entry of this jar needs to be signed by at least // one signer for the jar to be considered successfully signed. - final VerifyResult result; + final SignVerifyResult result; if (numSignableEntriesInJar == 0) { // Allow jars with no signable entries to simply be considered signed. // There should be no security risk in doing so. - result = VerifyResult.SIGNED_OK; + result = SignVerifyResult.SIGNED_OK; } else if (allEntriesSignedBySingleCert) { // We need to find at least one signer without any issues. result = verifySigners(jarSignCount); } else { - result = VerifyResult.UNSIGNED; + result = SignVerifyResult.UNSIGNED; } LOG.debug("Jar found at {} has been verified as {}", jarPath, result); return result; } - private boolean isTrustedTsa(CertPath certPath) { - if (certPath == null) { - return false; + + /** + * @return true if there are no signable entries in the jar. + * This will return false if any of verified jars have content more than just META-INF/. + */ + private boolean isTriviallySigned() { + return getTotalJarEntries(jarSignableEntries) <= 0 && certs.size() <= 0; + } + + private boolean isSigned() { + final boolean fullySigned = isFullySigned(certs, jarSignableEntries); + LOG.debug("App already has trusted publisher: {}", fullySigned); + return fullySigned; + } + + /** + * Verify the jars provided and update the state of this instance to match the new information. + * + * @param jars List of new jars to be verified. + * @param tracker Resource tracker used to obtain the the jars from cache + * @throws Exception Caused by issues with obtaining the jars' entries or interacting with the tracker. + */ + private void verifyJars(final List jars, final ResourceTracker tracker) throws Exception { + + for (JARDesc jar : jars) { + final File jarFile = tracker.getCacheFile(jar.getLocation()); + + // some sort of resource download/cache error. Nothing to add + // in that case ... but don't fail here + if (jarFile == null || !jarFile.isFile()) { + continue; + } + + final String jarPath = jarFile.getCanonicalFile().getAbsolutePath(); + if (verifiedJars.contains(jarPath) || unverifiedJars.contains(jarPath)) { + continue; + } + + final SignVerifyResult result = verifyJar(jarPath); + if (result == SignVerifyResult.UNSIGNED) { + unverifiedJars.add(jarPath); + } else if (result == SignVerifyResult.SIGNED_NOT_OK) { + verifiedJars.add(jarPath); + } else if (result == SignVerifyResult.SIGNED_OK) { + verifiedJars.add(jarPath); + } } - final List caKeyStores = KeyStores.getCAKeyStores(); - // Check entire cert path for a trusted CA - for (final Certificate c : certPath.getCertificates()) { - if (c instanceof X509Certificate) { - final X509Certificate x509 = (X509Certificate) c; - if (CertificateUtils.inKeyStores(x509, caKeyStores)) { - return true; + + for (CertPath certPath : certs.keySet()) { + checkTrustedCerts(certPath); + } + } + + /** + * Checks through all the jar entries of jarName for signers, storing all the common ones in the certs hash map. + * + * @param jarPath The absolute path to the jar file. + * @return The return of {@link JarCertVerifier#verifyJarEntryCerts} using the entries found in the jar located at jarName. + */ + private SignVerifyResult verifyJar(final String jarPath) { + try (final JarFile jarFile = new JarFile(jarPath, true)) { + final List entries = new ArrayList<>(); + final byte[] buffer = new byte[8192]; + + final Enumeration entriesEnum = jarFile.entries(); + while (entriesEnum.hasMoreElements()) { + final JarEntry entry = entriesEnum.nextElement(); + entries.add(entry); + + try (InputStream is = jarFile.getInputStream(entry)) { + //noinspection StatementWithEmptyBody + while (is.read(buffer, 0, buffer.length) != -1) { + // we just read. this will throw a SecurityException + // if a signature/digest check fails. + } } } + return verifyJarEntryCerts(jarPath, jarFile.getManifest() != null, entries); + } catch (Exception e) { + LOG.error("Error in verify jar " + jarPath, e); + throw new RuntimeException("Error in verify jar " + jarPath, e); } - return false; } - private VerifyResult verifySigners(final Map jarSignCount) { + private SignVerifyResult verifySigners(final Map jarSignCount) { for (CertPath entryCertPath : jarSignCount.keySet()) { if (certs.containsKey(entryCertPath) && !certs.get(entryCertPath).hasSigningIssues()) { - return VerifyResult.SIGNED_OK; + return SignVerifyResult.SIGNED_OK; } } // All signers had issues - return VerifyResult.SIGNED_NOT_OK; + return SignVerifyResult.SIGNED_NOT_OK; } private ZonedDateTime zonedDateTime(final Date date) { - if (date == null) { - return ZonedDateTime.now(); - } return date.toInstant().atZone(ZoneId.systemDefault()); } @@ -482,9 +536,6 @@ private void checkTrustedCerts(final CertPath certPath) { } } } catch (Exception e) { - // TODO: Warn user about not being able to - // look through their cacerts/trusted.certs - // file depending on exception. LOG.warn("Unable to read through cert store files."); throw e; } @@ -493,62 +544,10 @@ private void checkTrustedCerts(final CertPath certPath) { info.setUntrusted(); } - public void setCurrentlyUsedCertPath(final CertPath certPath) { + private void setCurrentlyUsedCertPath(final CertPath certPath) { currentlyUsed = certPath; } - @Override - public Certificate getPublisher(final CertPath certPath) { - if (certPath != null) { - currentlyUsed = certPath; - } - if (currentlyUsed != null) { - final List certList = currentlyUsed.getCertificates(); - if (certList.size() > 0) { - return certList.get(0); - } else { - return null; - } - } else { - return null; - } - } - - @Override - public Certificate getRoot(final CertPath certPath) { - if (certPath != null) { - currentlyUsed = certPath; - } - if (currentlyUsed != null) { - final List certList = currentlyUsed.getCertificates(); - if (certList.size() > 0) { - return certList.get(certList.size() - 1); - } else { - return null; - } - } else { - return null; - } - } - - /** - * Returns whether a file is in META-INF, and thus does not require signing. - *

    - * Signature-related files under META-INF include: . META-INF/MANIFEST.MF . META-INF/SIG-* . META-INF/*.SF . META-INF/*.DSA . META-INF/*.RSA - */ - static boolean isMetaInfFile(final String name) { - if (name.endsWith("class")) { - return false; - } - return name.startsWith(META_INF) && ( - name.endsWith(".MF") || - name.endsWith(".SF") || - name.endsWith(".DSA") || - name.endsWith(".RSA") || - SIG.matcher(name).matches() - ); - } - /** * Check if userCert is designed to be a code signer * @@ -600,21 +599,131 @@ private void checkCertUsage(final CertPath certPath, final X509Certificate userC } } + private Map getJarSignableEntries() { + return Collections.unmodifiableMap(jarSignableEntries); + } + /** - * Returns if all jars are signed. + * Checks if the app has already found trust in its publisher(s). * - * @return True if all jars are signed, false if there are one or more unsigned jars + * @param certs The certs to search through and their cert information + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return True if the app trusts its publishers. */ - public boolean allJarsSigned() { - return unverifiedJars.isEmpty(); + private boolean hasAlreadyTrustedPublisher( + Map certs, + Map signedJars) { + int sumOfSignableEntries = getTotalJarEntries(signedJars); + for (CertInformation certInfo : certs.values()) { + Map certSignedJars = certInfo.getSignedJars(); + + if (getTotalJarEntries(certSignedJars) == sumOfSignableEntries + && certInfo.isPublisherAlreadyTrusted()) { + return true; + } + } + return false; } - public void checkTrustWithUser(final SecurityDelegate securityDelegate, final JNLPFile file) throws LaunchException { - appVerifier.checkTrustWithUser(securityDelegate, this, file); + /** + * Checks if the app has signer(s) whose certs along their chains are in CA certs. + * + * @param certs The certs to search through and their cert information + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return True if the app has a root in the CA certs store. + */ + private boolean hasRootInCacerts(Map certs, + Map signedJars) { + int sumOfSignableEntries = getTotalJarEntries(signedJars); + for (CertInformation certInfo : certs.values()) { + Map certSignedJars = certInfo.getSignedJars(); + + if (getTotalJarEntries(certSignedJars) == sumOfSignableEntries + && certInfo.isRootInCacerts()) { + return true; + } + } + return false; } - public Map getJarSignableEntries() { - return Collections.unmodifiableMap(jarSignableEntries); + /** + * Checks if the app's jars are covered by the provided certificates, enough + * to consider the app fully signed. + * + * @param certs Any possible signer and their respective information regarding this app. + * @param signedJars A map of all the jars of this app and the number of + * signed entries each one has. + * @return true if jar is fully signed + */ + private boolean isFullySigned(Map certs, + Map signedJars) { + int sumOfSignableEntries = getTotalJarEntries(signedJars); + for (CertPath cPath : certs.keySet()) { + // If this cert has signed everything, return true + if (hasCompletelySignedApp(certs.get(cPath), sumOfSignableEntries)) { + return true; + } + } + + // No cert found that signed all entries. Return false. + return false; + } + + /** + * Prompt the user with requests for trusting the certificates used by this app + * + * @param securityDelegate parental security + * @param jcv jar verifier + * @param file jnlp file to provide information + * @throws LaunchException if it fails to verify + */ + private void checkTrustWithUser(SecurityDelegate securityDelegate, JarCertVerifier jcv, JNLPFile file) + throws LaunchException { + + int sumOfSignableEntries = getTotalJarEntries(jcv.getJarSignableEntries()); + for (CertPath cPath : jcv.getCertsList()) { + jcv.setCurrentlyUsedCertPath(cPath); + CertInformation info = jcv.getCertInformation(cPath); + if (hasCompletelySignedApp(info, sumOfSignableEntries)) { + if (info.isPublisherAlreadyTrusted()) { + return; + } + + AccessType dialogType; + if (info.isRootInCacerts() && !info.hasSigningIssues()) { + dialogType = AccessType.VERIFIED; + } else if (info.isRootInCacerts()) { + dialogType = AccessType.SIGNING_ERROR; + } else { + dialogType = AccessType.UNVERIFIED; + } + + YesNoSandbox action = Dialogs.showCertWarningDialog( + dialogType, file, jcv, securityDelegate); + if (action != null && action.toBoolean()) { + if (action.compareValue(Primitive.SANDBOX)) { + securityDelegate.setRunInSandbox(); + } + return; + } + } + } + + throw new LaunchException(null, null, FATAL, "Launch Error", + "Cancelled on user request.", ""); + } + + /** + * Find out if the CertPath with the given info has fully signed the app. + * + * @param info The information regarding the CertPath in question + * @param sumOfSignableEntries The total number of signable entries in the app. + * @return True if the signer has fully signed this app. + */ + private boolean hasCompletelySignedApp(CertInformation info, int sumOfSignableEntries) { + return getTotalJarEntries(info.getSignedJars()) == sumOfSignableEntries; } /** @@ -623,9 +732,13 @@ public Map getJarSignableEntries() { * @param map map of all jars * @return The number of entries. */ - public static int getTotalJarEntries(final Map map) { + private int getTotalJarEntries(final Map map) { return map.values().stream() .mapToInt(Integer::intValue) .sum(); } + + enum SignVerifyResult { + UNSIGNED, SIGNED_OK, SIGNED_NOT_OK + } } diff --git a/core/src/main/java/net/sourceforge/jnlp/signing/NewJarCertVerifier.java b/core/src/main/java/net/sourceforge/jnlp/signing/NewJarCertVerifier.java new file mode 100644 index 000000000..fc60404ea --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/signing/NewJarCertVerifier.java @@ -0,0 +1,67 @@ +package net.sourceforge.jnlp.signing; + +import java.io.File; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static net.sourceforge.jnlp.signing.SignVerifyUtils.determineCertificatesFullySigningThe; + +/** + * All jars and their fully signing certificates which have been loaded for the application. + */ +public class NewJarCertVerifier { + + private final Map jarToFullySigningCertificates = new HashMap<>(); + + public Map verify() { + return jarToFullySigningCertificates.values().stream() + .flatMap(r -> r.getCertificatePaths().stream()) + .collect(Collectors.toSet()).stream() + .collect(Collectors.toMap(Function.identity(), this::getStateForSingleCertificate)); + } + + private ApplicationSigningState getStateForSingleCertificate(final CertPath certPath) { + final Certificate certificate = certPath.getCertificates().get(0); + final long numFullySignedJars = jarToFullySigningCertificates.values().stream() + .filter(certs -> certs.contains(certificate)) + .count(); + + if (numFullySignedJars == jarToFullySigningCertificates.size()) { + return ApplicationSigningState.FULL; + } + + return numFullySignedJars == 0 ? ApplicationSigningState.NONE : ApplicationSigningState.PARTIAL; + } + + public void addAll(final List jars) { + for (File jarFile : jars) { + add(jarFile); + } + } + + public void add(final File jarFile) { + final CertificatesFullySigningTheJar certificatesFullySigningTheJar = determineCertificatesFullySigningThe(jarFile); + jarToFullySigningCertificates.put(jarFile, certificatesFullySigningTheJar); + } + + public boolean isNotFullySigned() { + return getFullySigningCertificates().isEmpty(); + } + + public Set getFullySigningCertificates() { + return verify().entrySet().stream() + .filter(e -> e.getValue() == ApplicationSigningState.FULL) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + public CertificatesFullySigningTheJar certificatesSigning(final File jarFile) { + return jarToFullySigningCertificates.get(jarFile); + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/signing/SignVerifyUtils.java b/core/src/main/java/net/sourceforge/jnlp/signing/SignVerifyUtils.java new file mode 100644 index 000000000..f856f7f8e --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/signing/SignVerifyUtils.java @@ -0,0 +1,231 @@ +package net.sourceforge.jnlp.signing; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.sourceforge.jnlp.security.CertificateUtils; +import net.sourceforge.jnlp.security.KeyStores; +import net.sourceforge.jnlp.tools.CertInformation; +import net.sourceforge.jnlp.util.JarFile; +import sun.security.util.DerInputStream; +import sun.security.util.DerValue; +import sun.security.x509.NetscapeCertTypeExtension; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.CodeSigner; +import java.security.KeyStore; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.time.temporal.ChronoUnit.MONTHS; + +public class SignVerifyUtils { + + private static final Logger LOG = LoggerFactory.getLogger(SignVerifyUtils.class); + + private static final String META_INF = "META-INF/"; + + private static final Pattern SIG = Pattern.compile(".*" + META_INF + "SIG-.*"); + + /** + * Returns whether a file is in META-INF, and thus does not require signing. + *

    + * Signature-related files under META-INF include: . META-INF/MANIFEST.MF . META-INF/SIG-* . META-INF/*.SF . META-INF/*.DSA . META-INF/*.RSA + */ + static boolean isMetaInfFile(final String name) { + if (name.endsWith("class")) { + return false; + } + return name.startsWith(META_INF) && ( + name.endsWith(".MF") || + name.endsWith(".SF") || + name.endsWith(".DSA") || + name.endsWith(".RSA") || + SIG.matcher(name).matches() + ); + } + + static CertificatesFullySigningTheJar determineCertificatesFullySigningThe(final File file) { + Assert.requireNonNull(file, "file"); + + try (final JarFile jarFile = new JarFile(file, true)) { + final List entries = new ArrayList<>(); + final byte[] buffer = new byte[8192]; + + //CHECK: Read full Jar and see if a SecurityException happens + + final Enumeration entriesEnum = jarFile.entries(); + while (entriesEnum.hasMoreElements()) { + final JarEntry entry = entriesEnum.nextElement(); + entries.add(entry); + + try (InputStream is = jarFile.getInputStream(entry)) { + //noinspection StatementWithEmptyBody + while (is.read(buffer, 0, buffer.length) != -1) { + // we just read. this will throw a SecurityException + // if a signature/digest check fails. + } + } + } + + //Now we handle the certs + final Map jarSignCount = new HashMap<>(); + int numSignableEntriesInJar = 0; + final boolean jarHasManifest = jarFile.getManifest() != null; + + if (jarHasManifest) { + for (JarEntry je : entries) { + final boolean isSignable = !je.isDirectory() && !isMetaInfFile(je.getName()); + if (isSignable) { + numSignableEntriesInJar++; + final CodeSigner[] signers = je.getCodeSigners(); + if (signers != null) { + for (final CodeSigner signer : signers) { + final CertPath certPath = signer.getSignerCertPath(); + jarSignCount.putIfAbsent(certPath, 0); + jarSignCount.computeIfPresent(certPath, (cp, count) -> count + 1); + } + } + } + } + } else { + // set to 1 so that unsigned jars with no manifests can't sneak in + numSignableEntriesInJar = 1; + } + + // Find all signers that have signed every signable entry in this jar. + final int x = numSignableEntriesInJar; + final Set result = jarSignCount.entrySet().stream() + .filter(entry -> entry.getValue() == x) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + return new CertificatesFullySigningTheJar(file, result); + } catch (Exception e) { + throw new RuntimeException("Error in verify jar " + file, e); + } + } + + public static CertInformation calculateCertInformationFor(CertPath certPath, ZonedDateTime now) { + final CertInformation result = new CertInformation(); + final Certificate certificate = certPath.getCertificates().get(0); + if (certificate instanceof X509Certificate) { + final X509Certificate x509Certificate = (X509Certificate) certificate; + checkCertUsage(x509Certificate, result); + checkExpiration(x509Certificate, now, result); + } + checkTrustedCerts(certPath, result); + return result; + } + + private static void checkExpiration(final X509Certificate cert, final ZonedDateTime now, final CertInformation certInfo) { + final ZonedDateTime notBefore = zonedDateTime(cert.getNotBefore()); + final ZonedDateTime notAfter = zonedDateTime(cert.getNotAfter()); + if (now.isBefore(notBefore)) { + certInfo.setNotYetValidCert(); + } + if (now.isAfter(notAfter)) { + certInfo.setHasExpiredCert(); + } else if (now.plus(6, MONTHS).isAfter(notAfter)) { + certInfo.setHasExpiringCert(); + } + } + + private static ZonedDateTime zonedDateTime(final Date date) { + return date.toInstant().atZone(ZoneId.systemDefault()); + } + + private static void checkCertUsage(final X509Certificate userCert, final CertInformation certInformation) { + + // Can act as a signer? + // 1. if KeyUsage, then [0] should be true + // 2. if ExtendedKeyUsage, then should contains ANY or CODE_SIGNING + // 3. if NetscapeCertType, then should contains OBJECT_SIGNING + // 1,2,3 must be true + + final boolean[] keyUsage = userCert.getKeyUsage(); + if (keyUsage != null) { + if (keyUsage.length < 1 || !keyUsage[0]) { + certInformation.setBadKeyUsage(); + } + } + + try { + final List xKeyUsage = userCert.getExtendedKeyUsage(); + if (xKeyUsage != null) { + if (!xKeyUsage.contains("2.5.29.37.0") // anyExtendedKeyUsage + && !xKeyUsage.contains("1.3.6.1.5.5.7.3.3")) { // codeSigning + certInformation.setBadExtendedKeyUsage(); + } + } + } catch (java.security.cert.CertificateParsingException e) { + // shouldn't happen + } + + try { + // OID_NETSCAPE_CERT_TYPE + final byte[] netscapeEx = userCert.getExtensionValue("2.16.840.1.113730.1.1"); + if (netscapeEx != null) { + final DerInputStream in = new DerInputStream(netscapeEx); + final byte[] raw = in.getOctetString(); + final byte[] encoded = new DerValue(raw).getUnalignedBitString().toByteArray(); + + final NetscapeCertTypeExtension extn = new NetscapeCertTypeExtension(encoded); + + if (!extn.get(NetscapeCertTypeExtension.OBJECT_SIGNING)) { + certInformation.setBadNetscapeCertType(); + } + } + } catch (IOException e) { + // + } + } + + /** + * Checks the user's trusted.certs file and the cacerts file to see if a + * publisher's and/or CA's certificate exists there. + * + * @param certPath The cert path of the signer being checked for trust. + */ + private static void checkTrustedCerts(final CertPath certPath, final CertInformation info) { + try { + final X509Certificate publisher = (X509Certificate) certPath.getCertificates().get(0); + final List certKeyStores = KeyStores.getCertKeyStores(); + if (CertificateUtils.inKeyStores(publisher, certKeyStores)) { + info.setAlreadyTrustPublisher(); + } + final List caKeyStores = KeyStores.getCAKeyStores(); + // Check entire cert path for a trusted CA + for (final Certificate c : certPath.getCertificates()) { + if (c instanceof X509Certificate) { + final X509Certificate x509 = (X509Certificate) c; + if (CertificateUtils.inKeyStores(x509, caKeyStores)) { + info.setRootInCacerts(); + return; + } + } + } + } catch (Exception e) { + LOG.warn("Unable to read through cert store files."); + throw e; + } + + // Otherwise a parent cert was not found to be trusted. + info.setUntrusted(); + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/tools/CertInformation.java b/core/src/main/java/net/sourceforge/jnlp/tools/CertInformation.java index 7732a48b1..32a700d40 100644 --- a/core/src/main/java/net/sourceforge/jnlp/tools/CertInformation.java +++ b/core/src/main/java/net/sourceforge/jnlp/tools/CertInformation.java @@ -51,11 +51,9 @@ */ public class CertInformation { - private final static Logger LOG = LoggerFactory.getLogger(CertInformation.class); + private static final Logger LOG = LoggerFactory.getLogger(CertInformation.class); private boolean hasExpiredCert = false; - private boolean hasExpiringCert = false; - private boolean isNotYetValidCert = false; /* Code signer properties of the certificate. */ @@ -66,7 +64,7 @@ public class CertInformation { private boolean alreadyTrustPublisher = false; private boolean rootInCacerts = false; - static enum Detail { + enum Detail { TRUSTED (R("STrustedCertificate")), UNTRUSTED (R("SUntrustedCertificate")), RUN_WITHOUT_RESTRICTIONS(R("SRunWithoutRestrictions")), @@ -90,7 +88,7 @@ public String message() { private EnumSet details = EnumSet.noneOf(Detail.class); /** The jars and their number of entries this cert has signed. */ - private HashMap signedJars = new HashMap(); + private HashMap signedJars = new HashMap<>(); /** * Return if there are signing issues with this certificate. @@ -144,14 +142,6 @@ public void resetForReverification() { removeFromDetails(Detail.UNTRUSTED); removeFromDetails(Detail.TRUSTED); } - /** - * Check if this cert is the signer of a jar. - * @param jarName The absolute path of the jar this certificate has signed. - * @return {@code true} if this cert has signed the jar found at {@code jarName}. - */ - public boolean isSignerOfJar(String jarName) { - return signedJars.containsKey(jarName); - } /** * Add a jar to the list of jars this certificate has signed along with the @@ -168,15 +158,6 @@ public void setNumJarEntriesSigned(String jarName, int signedEntriesCount) { } } - /** - * Find the number of entries this cert has signed in the specified jar. - * @param jarName The absolute path of the jar this certificate has signed. - * @return The number of entries this cert has signed in {@code jarName}. - */ - public int getNumJarEntriesSigned(String jarName) { - return signedJars.get(jarName); - } - /** * Get all the jars this cert has signed along with the number of entries * in each jar. @@ -192,7 +173,7 @@ public Map getSignedJars() { * @return A list of all the details/issues with this app. */ public List getDetailsAsStrings() { - List detailsToStr = new ArrayList(); + List detailsToStr = new ArrayList<>(); for (Detail issue : details) { detailsToStr.add(issue.message()); } @@ -223,19 +204,10 @@ public void setHasExpiredCert() { * the list of details. */ public void setHasExpiringCert() { - hasExpiringCert = true; details.add(Detail.RUN_WITHOUT_RESTRICTIONS); details.add(Detail.EXPIRING); } - /** - * Get whether or not this cert will expire within 6 months. - * @return {@code true} if the cert will be expired after 6 months. - */ - public boolean hasExpiringCert() { - return hasExpiringCert; - } - /** * Set that this cert is not yet valid * and add this issue to the list of details. diff --git a/core/src/main/java/net/sourceforge/jnlp/util/GenericDesktopEntry.java b/core/src/main/java/net/sourceforge/jnlp/util/GenericDesktopEntry.java index 0a15e4095..6ffb42823 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/GenericDesktopEntry.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/GenericDesktopEntry.java @@ -17,7 +17,7 @@ */ package net.sourceforge.jnlp.util; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; import java.io.File; import java.io.IOException; @@ -32,7 +32,7 @@ public interface GenericDesktopEntry { //linux - void createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult menu, AccessWarningPaneComplexReturn.ShortcutResult desktop); + void createDesktopShortcuts(ShortcutResult menu, ShortcutResult desktop); void refreshExistingShortcuts(boolean desktop, boolean menu); diff --git a/core/src/main/java/net/sourceforge/jnlp/util/LocaleUtils.java b/core/src/main/java/net/sourceforge/jnlp/util/LocaleUtils.java index a26924d94..d7fbc9a97 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/LocaleUtils.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/LocaleUtils.java @@ -35,7 +35,7 @@ public static Locale getLocale(final String localeString) throws ParseException return new Locale(language, country, variant); } - public static boolean localeMatches(final Locale[] availableLocales, final Locale locale) { + public static boolean localeMatches(final Locale locale, final Locale... availableLocales) { return Stream.of(Match.values()) .anyMatch(match -> localMatches(locale, match, availableLocales == null ? new Locale[0] : availableLocales)); } diff --git a/core/src/main/java/net/sourceforge/jnlp/util/WindowsDesktopEntry.java b/core/src/main/java/net/sourceforge/jnlp/util/WindowsDesktopEntry.java index 6fc03ca5f..c335aaddf 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/WindowsDesktopEntry.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/WindowsDesktopEntry.java @@ -22,7 +22,7 @@ import net.adoptopenjdk.icedteaweb.jvm.JvmUtils; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; import net.sourceforge.jnlp.JNLPFile; import java.io.File; @@ -182,7 +182,7 @@ private boolean needToAddNewShortcutEntry(String path, List lines) { } @Override - public void createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult menu, AccessWarningPaneComplexReturn.ShortcutResult desktop) { + public void createDesktopShortcuts(ShortcutResult menu, ShortcutResult desktop) { throw new UnsupportedOperationException("not supported on windows like systems"); } diff --git a/core/src/main/java/net/sourceforge/jnlp/util/XDesktopEntry.java b/core/src/main/java/net/sourceforge/jnlp/util/XDesktopEntry.java index 2dd95953b..4c5e4057b 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/XDesktopEntry.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/XDesktopEntry.java @@ -26,6 +26,7 @@ import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.os.OsUtil; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.AccessWarningPaneComplexReturn; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.ShortcutResult; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.config.PathsAndFiles; @@ -121,7 +122,7 @@ public XDesktopEntry(JNLPFile file) { * @param info result of user's interference * @return string with desktop shortcut specification */ - String getContent(boolean menu, AccessWarningPaneComplexReturn.ShortcutResult info) { + String getContent(boolean menu, ShortcutResult info) { File generatedJnlp = null; String fileContents = "[Desktop Entry]\n"; @@ -197,7 +198,7 @@ File getShortcutTmpFile() { * @param desktop how to create on desktop */ @Override - public void createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult menu, AccessWarningPaneComplexReturn.ShortcutResult desktop) { + public void createDesktopShortcuts(ShortcutResult menu, ShortcutResult desktop) { boolean isDesktop = false; if (desktop != null && desktop.isCreate()) { isDesktop = true; @@ -229,7 +230,7 @@ public void createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult /** * Install this XDesktopEntry into the user's menu. */ - private void installMenuLauncher(AccessWarningPaneComplexReturn.ShortcutResult info) { + private void installMenuLauncher(ShortcutResult info) { //TODO add itweb-settings tab which allows to remove individual items/icons try { File f = getLinuxMenuIconFile(); @@ -245,7 +246,7 @@ private void installMenuLauncher(AccessWarningPaneComplexReturn.ShortcutResult i /** * Install this XDesktopEntry into the user's desktop as a launcher. */ - private void installDesktopLauncher(AccessWarningPaneComplexReturn.ShortcutResult info) { + private void installDesktopLauncher(ShortcutResult info) { File shortcutFile = getShortcutTmpFile(); try { diff --git a/core/src/main/java/net/sourceforge/jnlp/util/logging/OutputController.java b/core/src/main/java/net/sourceforge/jnlp/util/logging/OutputController.java index a1f257fe0..257008408 100644 --- a/core/src/main/java/net/sourceforge/jnlp/util/logging/OutputController.java +++ b/core/src/main/java/net/sourceforge/jnlp/util/logging/OutputController.java @@ -252,7 +252,7 @@ private static class SystemLogHolder { private static SingleStreamLogger initSystemLogger() { if (OsUtil.isWindows()) { - return new WinSystemLog(); + return new DummyLogger(); } else { return new UnixSystemLog(); } diff --git a/core/src/main/java/net/sourceforge/jnlp/util/logging/WinSystemLog.java b/core/src/main/java/net/sourceforge/jnlp/util/logging/WinSystemLog.java deleted file mode 100644 index 52f31a73e..000000000 --- a/core/src/main/java/net/sourceforge/jnlp/util/logging/WinSystemLog.java +++ /dev/null @@ -1,37 +0,0 @@ -/*Copyright (C) 2013 Red Hat, Inc. - - This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp.util.logging; - -public class WinSystemLog extends DummyLogger { -} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoaderTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoaderTest.java new file mode 100644 index 000000000..707defbd3 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/JnlpApplicationClassLoaderTest.java @@ -0,0 +1,223 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class JnlpApplicationClassLoaderTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void findClass1() throws Exception { + + //given + final PartsHandler partsHandler = createDummyPartsHandlerFor("empty.jnlp"); + + //expect + thrown.expect(ClassNotFoundException.class); + thrown.expectMessage("not.in.Classpath"); + + //when + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("not.in.Classpath"); + } + + @Test + public void findClass4() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("eager-and-lazy.jnlp"); + + //when + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + + //than + assertTrue(partsHandler.hasTriedToDownload("eager.jar")); + assertFalse(partsHandler.hasTriedToDownload("lazy.jar")); + } + + @Test + public void findClass5() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("eager-and-lazy.jnlp"); + + //when + try { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("class.in.lazy.Package"); + } catch (final Exception ignore) {} + + //than + assertTrue(partsHandler.hasTriedToDownload("eager.jar")); + assertTrue(partsHandler.hasTriedToDownload("lazy.jar")); + } + + @Test + public void findClass6() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-not-recursive.jnlp"); + + //when + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + + //than + assertEquals(0, partsHandler.getDownloaded().size()); + } + + @Test + public void findClass7() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-not-recursive.jnlp"); + + //when + try { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("class.in.lazy.A"); + } catch (final Exception ignore) {} + + //than + assertEquals(1, partsHandler.getDownloaded().size()); + } + + @Test + public void findClass8() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-not-recursive.jnlp"); + + //when + try { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("class.in.lazy.sub.A"); + } catch (final Exception ignore) {} + + //than + assertEquals(0, partsHandler.getDownloaded().size()); + } + + @Test + public void findClass9() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-recursive.jnlp"); + + //when + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + + //than + assertEquals(0, partsHandler.getDownloaded().size()); + } + + @Test + public void findClass10() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-recursive.jnlp"); + + //when + try { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("class.in.lazy.A"); + } catch (final Exception ignore) {} + + //than + assertEquals(1, partsHandler.getDownloaded().size()); + } + + @Test + public void findClass11() throws Exception { + + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("lazy-recursive.jnlp"); + + //when + try { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + classLoader.findClass("class.in.lazy.sub.A"); + } catch (final Exception ignore) {} + + //than + assertEquals(1, partsHandler.getDownloaded().size()); + } + + private static class DummyPartsHandler extends PartsHandler { + + private final List downloaded = new CopyOnWriteArrayList<>(); + + public DummyPartsHandler(final List parts) { + super(parts, new JnlpApplicationClassLoaderTest.DummyApplicationTrustValidator()); + } + + @Override + protected Optional getLocalUrlForJar(final JARDesc jarDesc) { + System.out.println("Should load " + jarDesc.getLocation()); + downloaded.add(jarDesc); + return Optional.ofNullable(jarDesc.getLocation()); + } + + public boolean hasTriedToDownload(final String name) { + return downloaded.stream() + .anyMatch(jar -> jar.getLocation().toString().endsWith(name)); + } + + public List getDownloaded() { + return Collections.unmodifiableList(downloaded); + } + + } + + private static class DummyApplicationTrustValidator implements ApplicationTrustValidator { + @Override + public void validateEagerJars(List jars) { + } + + @Override + public void validateLazyJars(List jars) { + } + } + + public static DummyPartsHandler createDummyPartsHandlerFor(final String name) throws IOException, ParseException { + final JNLPFile file = createFile(name); + final List parts = createFor(file).getParts(); + return new DummyPartsHandler(parts); + } + + public static JNLPFile createFile(final String name) throws IOException, ParseException { + final JNLPFileFactory jnlpFileFactory = new JNLPFileFactory(); + return jnlpFileFactory.create(JnlpApplicationClassLoaderTest.class.getResource(name)); + } + + public static PartExtractor createFor(final JNLPFile file) { + final JNLPFileFactory jnlpFileFactory = new JNLPFileFactory(); + return new PartExtractor(file, jnlpFileFactory); + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractorTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractorTest.java new file mode 100644 index 000000000..52df9c54c --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/classloader/PartExtractorTest.java @@ -0,0 +1,198 @@ +package net.adoptopenjdk.icedteaweb.classloader; + +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PackageDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +/** + * Test that the right parts, packages and jars are extracted from JNLP files. + */ +public class PartExtractorTest { + + private static final String DEFAULT_NAME = null; + private static final boolean LAZY = true; + private static final boolean EAGER = false; + private static final List> NO_JARS = emptyList(); + private static final List> NO_PACKAGES = emptyList(); + + private JNLPFileFactory jnlpFileFactory; + + @Before + public final void setUp() { + jnlpFileFactory = new JNLPFileFactory(); + } + + @Test + public void jnlpWithNoJars() throws Exception { + // given + final JNLPFile jnlpFile = new JNLPFileFactory().create(getUrl("empty.jnlp")); + + // when + final List parts = new PartExtractor(jnlpFile, jnlpFileFactory).getParts(); + + // then + assertThat(parts, containsInAnyOrder( + part(DEFAULT_NAME, LAZY, NO_JARS, NO_PACKAGES), + part(DEFAULT_NAME, EAGER, NO_JARS, NO_PACKAGES) + )); + } + + @Test + public void jnlpWithOneEagerAndOneUnnamedLazyJar() throws Exception { + // given + final JNLPFile jnlpFile = new JNLPFileFactory().create(getUrl("eager-and-unnamedLazy.jnlp")); + + // when + final List parts = new PartExtractor(jnlpFile, jnlpFileFactory).getParts(); + + // then + assertThat(parts, containsInAnyOrder( + part(DEFAULT_NAME, LAZY, jars("lazy.jar"), NO_PACKAGES), + part(DEFAULT_NAME, EAGER, jars("eager.jar"), NO_PACKAGES) + )); + } + + @Test + public void jnlpWithOneEagerAndOneLazyNamedJar() throws Exception { + // given + final JNLPFile jnlpFile = new JNLPFileFactory().create(getUrl("eager-and-lazy.jnlp")); + + // when + final List parts = new PartExtractor(jnlpFile, jnlpFileFactory).getParts(); + + // then + assertThat(parts, containsInAnyOrder( + part(DEFAULT_NAME, LAZY, NO_JARS, NO_PACKAGES), + part(DEFAULT_NAME, EAGER, jars("eager.jar"), NO_PACKAGES), + part("lazy-package", LAZY, jars("lazy.jar"), packages("class.in.lazy.Package")) + )); + } + + + //TODO: add the following test cases + // - extension with 'ext-part' and no 'part' and no 'download' => should make ext-part eager + // - extension with 'ext-part' and no 'part' 'download="lazy"' and package in extension => should make ext-part lazy + // - extension with 'ext-part' and no 'part' and 'download="lazy"' and no package in extension => should make ext-part eager + // - extension with 'ext-part' and 'part' and no 'download' => should combine the two parts and make it eager + // - extension with 'ext-part' and 'part' and no 'download = "lazy"' => should combine the two parts and make it lazy + // - extension with 'ext-part' and 'part' and no 'download = "lazy"' and neither part has a package => should combine the two parts and make it eager + + //TODO: add the following test cases + // - lazy and eager jar in same part => part should be eager + // - resource filtered by locale => jars should not be in result + // - resource filtered by os => jars should not be in result + // - resource filtered by arch => jars should not be in result + // - resource in tag with wrong version => jars should not be in result + // - extension without part mapping and different parts => should be 2 parts with different name + // - extension without part mapping and parts with same name => should be 2 parts with same name + // - a crazy nested example of all of the above: + // - resource with locale filter + // - in this a java element + // - in this a resource with arch filter + // - in this an extension + // - in this a resource with os filter + // - and finally the jar + + private Matcher part(String name, boolean lazy, List> jars, List> packages) { + return new BaseMatcher() { + @Override + public boolean matches(Object actual) { + if (actual instanceof Part) { + final Part part = (Part) actual; + final int expectedLength = UUID.randomUUID().toString().length(); + final boolean isDefaultName = part.getName().length() == expectedLength && Objects.equals(name, DEFAULT_NAME); + final boolean nameMatches = Objects.equals(part.getName(), name) || isDefaultName; + return nameMatches && + part.isLazy() == lazy && + matchesInAnyOrder(part.getJars(), jars) && + matchesInAnyOrder(part.getPackages(), packages); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("Part{name=" + name + " lazy=" + lazy + " jars=") + .appendList("[", ", ", "]", jars) + .appendText(" packages=") + .appendList("[", ", ", "]", packages) + .appendText("}"); + } + }; + } + + private boolean matchesInAnyOrder(List actual, List> matchers) { + return matchers.size() == actual.size() && + matchers.stream().allMatch(matcher -> actual.stream().anyMatch(matcher::matches)); + } + + private List> jars(String... jarNames) { + return Arrays.stream(jarNames) + .map(jarName -> { + final URL url = getUrl(jarName); + return new BaseMatcher() { + @Override + public boolean matches(Object actual) { + if (actual instanceof JARDesc) { + return ((JARDesc) actual).getLocation().equals(url); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(url.toString()); + } + }; + }) + .collect(Collectors.toList()); + } + + private List> packages(String... packageNames) { + return Arrays.stream(packageNames) + .map(packageName -> new BaseMatcher() { + @Override + public boolean matches(Object actual) { + if (actual instanceof PackageDesc) { + return ((PackageDesc) actual).getName().equals(packageName); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText(packageName); + } + }) + .collect(Collectors.toList()); + } + + private URL getUrl(String s) { + try { + final String selfClass = PartExtractorTest.class.getSimpleName() + ".class"; + final URL selfUrl = PartExtractorTest.class.getResource(selfClass); + final String result = selfUrl.toString().replace(selfClass, s); + return new URL(result); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/GetCommandTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/GetCommandTest.java index 2cb3a2166..0d5db1d5b 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/GetCommandTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/GetCommandTest.java @@ -49,7 +49,7 @@ public void testGetCommand() { // THEN ------------ assertEquals(SUCCESS, status); - assertThat(getOutContent(), containsString(KEY_SECURITY_LEVEL + ": " + "null")); + assertThat(getOutContent(), containsString(KEY_SECURITY_LEVEL + ": " + "HIGH")); } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/HelpCommandTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/HelpCommandTest.java index 3b5588984..827a65e83 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/HelpCommandTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/HelpCommandTest.java @@ -18,6 +18,7 @@ import net.adoptopenjdk.icedteaweb.JavaSystemProperties; import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; +import org.junit.Ignore; import net.sourceforge.jnlp.util.logging.LogConfig; import org.junit.After; import org.junit.Before; @@ -49,11 +50,12 @@ public void setUp() { public void tearDown() { setEnableLogging(originalEnableLogging); } - + /** * Test whether the {@code -help}, command executes and terminates with {@link CommandLine#SUCCESS}. */ @Test + @Ignore public void testHelpCommand() { // GIVEN ----------- final String[] args = {"-help"}; // use literals for readability diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/InfoCommandTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/InfoCommandTest.java index 94a94cd83..1e15724d0 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/InfoCommandTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/InfoCommandTest.java @@ -16,11 +16,12 @@ package net.adoptopenjdk.icedteaweb.client.commandline; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; +import net.adoptopenjdk.icedteaweb.security.SecurityLevel; import org.junit.Test; import java.util.Arrays; +import java.util.stream.Collectors; import static net.adoptopenjdk.icedteaweb.client.commandline.CommandLine.SUCCESS; import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_SECURITY_LEVEL; @@ -49,6 +50,12 @@ public void testInfoCommand() { // THEN ------------ assertEquals(SUCCESS, status); - assertThat(getOutContent(), containsString("Possible values " + AppletSecurityLevel.allToString())); + + final String strings = Arrays.stream(SecurityLevel.values()) + .map(Enum::name) + .collect(Collectors.toList()) + .toString(); + + assertThat(getOutContent(), containsString("Possible values " + strings)); } } \ No newline at end of file diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/ResetCommandTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/ResetCommandTest.java index 6d27f5ffb..e7708b2cb 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/ResetCommandTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/ResetCommandTest.java @@ -16,8 +16,8 @@ package net.adoptopenjdk.icedteaweb.client.commandline; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; +import net.adoptopenjdk.icedteaweb.security.SecurityLevel; import org.junit.Before; import org.junit.Test; @@ -45,14 +45,14 @@ public void beforeEach() { super.beforeEach(); // prepare some custom settings so that we have something to reset - getCommandLine(new String[]{"-set", "deployment.security.level", "ALLOW_UNSIGNED"}).handle(); + getCommandLine(new String[]{"-set", "deployment.security.level", "VERY_HIGH"}).handle(); getCommandLine(new String[]{"-set", "deployment.log", "true"}).handle(); } @Test public void testResetCommand() throws IOException { // GIVEN ----------- - assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + AppletSecurityLevel.ALLOW_UNSIGNED.name())); + assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + SecurityLevel.VERY_HIGH.name())); final String[] args = {"-reset", "deployment.security.level"}; // use literals for readability @@ -64,13 +64,13 @@ public void testResetCommand() throws IOException { // THEN ------------ assertEquals(SUCCESS, status); - assertThat(getUserDeploymentPropertiesFileContent(), not(containsString(AppletSecurityLevel.ALLOW_UNSIGNED.name()))); + assertThat(getUserDeploymentPropertiesFileContent(), not(containsString(SecurityLevel.VERY_HIGH.name()))); } @Test public void testResetCommandWithUnknownProperty() throws IOException { // GIVEN ----------- - assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + AppletSecurityLevel.ALLOW_UNSIGNED.name())); + assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + SecurityLevel.VERY_HIGH.name())); final String[] args = {"-reset", "unknown.setting.name"}; // use literals for readability @@ -87,7 +87,7 @@ public void testResetCommandWithUnknownProperty() throws IOException { @Test public void testResetAllCommand() throws IOException { // GIVEN ----------- - assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + AppletSecurityLevel.ALLOW_UNSIGNED.name())); + assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_SECURITY_LEVEL + "=" + SecurityLevel.VERY_HIGH.name())); assertThat(getUserDeploymentPropertiesFileContent(), containsString(KEY_ENABLE_DEBUG_LOGGING + "=" + "true")); final String[] args = {"-reset", "all"}; // use literals for readability @@ -100,7 +100,7 @@ public void testResetAllCommand() throws IOException { // THEN ------------ assertEquals(SUCCESS, status); - assertThat(getUserDeploymentPropertiesFileContent(), not(containsString(AppletSecurityLevel.ALLOW_UNSIGNED.name()))); + assertThat(getUserDeploymentPropertiesFileContent(), not(containsString(SecurityLevel.VERY_HIGH.name()))); assertThat(getUserDeploymentPropertiesFileContent(), not(containsString("true"))); } } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/SetCommandTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/SetCommandTest.java index fbbb7e0e6..483617234 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/SetCommandTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/commandline/SetCommandTest.java @@ -19,9 +19,9 @@ package net.adoptopenjdk.icedteaweb.client.commandline; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.commandline.CommandLineOptions; import net.adoptopenjdk.icedteaweb.commandline.UnevenParameterException; +import net.adoptopenjdk.icedteaweb.security.SecurityLevel; import org.junit.Test; import java.io.IOException; @@ -45,10 +45,10 @@ public class SetCommandTest extends AbstractCommandTest { @Test public void testSetCommand() throws IOException { // GIVEN ----------- - final String[] args = { "-set", "deployment.security.level", "ALLOW_UNSIGNED" }; // use literals for readability + final String[] args = { "-set", "deployment.security.level", "VERY_HIGH" }; // use literals for readability // test if literals still backed by constants - assertThat(Arrays.asList(args), contains(CommandLineOptions.SET.getOption(), KEY_SECURITY_LEVEL, AppletSecurityLevel.ALLOW_UNSIGNED.name())); + assertThat(Arrays.asList(args), contains(CommandLineOptions.SET.getOption(), KEY_SECURITY_LEVEL, SecurityLevel.VERY_HIGH.name())); // WHEN ------------ final int status = getCommandLine(args).handle(); @@ -56,7 +56,7 @@ public void testSetCommand() throws IOException { // THEN ------------ assertEquals(CommandLine.SUCCESS, status); assertTrue(getOutContent().isEmpty()); - assertThat(getUserDeploymentPropertiesFileContent(), containsString(AppletSecurityLevel.ALLOW_UNSIGNED.name())); + assertThat(getUserDeploymentPropertiesFileContent(), containsString(SecurityLevel.VERY_HIGH.name())); } @Test diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactoryTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactoryTest.java new file mode 100644 index 000000000..268274669 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/DefaultDialogFactoryTest.java @@ -0,0 +1,128 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.AppletWarningPane; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.MissingALACAttributePanel; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.apptrustwarningpanel.MatchingALACAttributePanel; +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.resources.Resource; +import net.adoptopenjdk.icedteaweb.resources.ResourceFactory; +import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; +import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.runtime.SecurityDelegateNew; +import net.sourceforge.jnlp.security.AccessType; +import net.sourceforge.jnlp.security.HttpsCertVerifier; +import net.sourceforge.jnlp.signing.JarCertVerifier; +import net.sourceforge.jnlp.util.UrlUtils; + +import javax.swing.JFrame; +import java.awt.BorderLayout; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.Set; + +/** + * Helper class to start dialogs without starting ITW. + */ +public class DefaultDialogFactoryTest { + + private final JNLPFile file; + private final HttpsCertVerifier httpsCertVerifier; + private final JarCertVerifier jarCertVerifier; + private final DefaultDialogFactory dialogFactory; + + public DefaultDialogFactoryTest() throws Exception { + JNLPRuntime.initialize(); + file = new JNLPFileFactory().create(getClass().getResource("/net/sourceforge/jnlp/basic.jnlp")); + httpsCertVerifier = new HttpsCertVerifier(new X509Certificate[0], true, true, "hostname"); + jarCertVerifier = new JarCertVerifier(); + dialogFactory = new DefaultDialogFactory(); + } + + public static void main(String[] args) throws Exception { + // new DefaultDialogFactoryTest().showCertWarning(); + // new DefaultDialogFactoryTest().showPartiallySignedWarning(); + // new DefaultDialogFactoryTest().showCertInfoDialog(); + // new DefaultDialogFactoryTest().showMoreInfoDialog(); + // new DefaultDialogFactoryTest().showMissingALACAttributePanel(); + // new DefaultDialogFactoryTest().showMatchingALACAttributePanel(); + // new DefaultDialogFactoryTest().showMissingPermissionsAttributeDialogue(); + + // new DefaultDialogFactoryTest().showAuthenticationPrompt(); + new DefaultDialogFactoryTest().show511Dialog(); + // new DefaultDialogFactoryTest().showAppletWarningPane(); -> applets only + } + + private void showCertInfoDialog() { + new DefaultDialogFactory().showCertInfoDialog(httpsCertVerifier, null); + } + + private void showAccessWarning() { + dialogFactory.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, file, new Object[]{"test"}); + } + + private void showCertWarning() { + dialogFactory.showCertWarningDialog(AccessType.UNVERIFIED, file, jarCertVerifier, null); + dialogFactory.showCertWarningDialog(AccessType.UNVERIFIED, file, httpsCertVerifier, null); + } + + private void showPartiallySignedWarning() { + dialogFactory.showPartiallySignedWarningDialog(file, jarCertVerifier, new SecurityDelegateNew(null, file)); + } + + private void showMissingALACAttributePanel() throws MalformedURLException { + Set s = new HashSet<>(); + s.add(new URL("http:/blah.com/blah")); + s.add(new URL("http:/blah.com/blah/blah")); + MissingALACAttributePanel w = new MissingALACAttributePanel(null, "HelloWorld", "http://nbblah.url", UrlUtils.setOfUrlsToHtmlList(s)); + JFrame f = new JFrame(); + f.setSize(600, 400); + f.add(w, BorderLayout.CENTER); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setVisible(true); + } + + private void showMatchingALACAttributePanel() throws MalformedURLException { + Set s = new HashSet<>(); + s.add(new URL("http:/blah.com/blah")); + s.add(new URL("http:/blah.com/blah/blah")); + MatchingALACAttributePanel w = new MatchingALACAttributePanel(null, file, "http://nbblah.url", UrlUtils.setOfUrlsToHtmlList(s)); + JFrame f = new JFrame(); + f.setSize(600, 400); + f.add(w, BorderLayout.CENTER); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setVisible(true); + } + + private void showMissingPermissionsAttributeDialogue() { + new DefaultDialogFactory().showMissingPermissionsAttributeDialogue(file); + } + + private void show511Dialog() throws MalformedURLException { + Resource resource = ResourceFactory.createResource(new URL("http://example.com/test.jar"), VersionString.fromString("1.0"), null, UpdatePolicy.ALWAYS); + + dialogFactory.show511Dialogue(resource); + } + + private void showMoreInfoDialog() throws IOException, ParseException { + new DefaultDialogFactory().showMoreInfoDialog(new JarCertVerifier(), file); + } + + private void showAppletWarningPane() { + AppletWarningPane w = new AppletWarningPane(null, null); + JFrame f = new JFrame(); + f.setSize(600, 400); + f.add(w, BorderLayout.CENTER); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setVisible(true); + } + + public void showAuthenticationPrompt() { + new DefaultDialogFactory().showAuthenticationPrompt("host", 666, "prompt", "type"); + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsHolder.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsHolder.java new file mode 100644 index 000000000..4c49e4472 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsHolder.java @@ -0,0 +1,22 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.DialogFactory; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; + +/** + * Helper class for manipulating the implementation of the {@link Dialogs}. + */ +public class SecurityDialogsHolder { + + /** + * The returned {@link AutoCloseable} must be called at the end of the test to allow other tests to set their own dialogs. + */ + public static RevertDialogsToDefault setSecurityDialogForTests(DialogFactory dialogs) { + return Dialogs.setDialogFactory(dialogs)::uninstall; + } + + public interface RevertDialogsToDefault extends AutoCloseable { + @Override + void close(); + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsTest.java index d72e491a7..62870a0ec 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogsTest.java @@ -33,6 +33,7 @@ */ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.Dialogs; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.impl.UnsignedAppletActionStorageImpl; @@ -216,11 +217,11 @@ public void testDialogsHeadlessTrustAllPrompt() throws Exception { testAllDialogs(ExpectedResults.PositiveResults); checkUnsignedActing(true); setAS(AppletSecurityLevel.ASK_UNSIGNED); - checkUnsignedActing(true, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_ALL); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); } finally { resetQueue(); } @@ -241,11 +242,11 @@ public void testDialogsHeadlessTrustNonePrompt() throws Exception { testAllDialogsNullResults(); checkUnsignedActing(true); setAS(AppletSecurityLevel.ASK_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_ALL); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); } finally { System.setIn(backup); resetQueue(); @@ -264,11 +265,11 @@ public void testDialogsNotHeadlessTrustAllDontPrompt() throws Exception { testAllDialogs(ExpectedResults.PositiveResults); checkUnsignedActing(true); setAS(AppletSecurityLevel.ASK_UNSIGNED); - checkUnsignedActing(true, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_ALL); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); } finally { resetQueue(); } @@ -286,11 +287,11 @@ public void testDialogsNotHeadlessTrustNoneDontPrompt() throws Exception { testAllDialogs(ExpectedResults.NegativeResults); checkUnsignedActing(true); setAS(AppletSecurityLevel.ASK_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_ALL); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); } finally { resetQueue(); } @@ -298,79 +299,58 @@ public void testDialogsNotHeadlessTrustNoneDontPrompt() throws Exception { private void testAllDialogs(ExpectedResults r) throws MalformedURLException { //anything but shortcut - AccessWarningPaneComplexReturn r1 = SecurityDialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); + AccessWarningPaneComplexReturn r1 = Dialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); Assert.assertEquals(r.p, r1.getRegularReturn().getValue()); //shortcut - AccessWarningPaneComplexReturn r2 = SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); + AccessWarningPaneComplexReturn r2 = Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); Assert.assertEquals(r.p, r2.getRegularReturn().getValue()); - YesNo r3 = SecurityDialogs.showUnsignedWarningDialog(crtJnlpF()); - Assert.assertEquals(r.ea, r3); //can't emulate security delegate now //YesNoSandbox r4 = SecurityDialogs.showCertWarningDialog(SecurityDialogs.AccessType.UNVERIFIED, crtJnlpF(), null, null); //Assert.assertEquals(r.p, r4.getValue()); //YesNo r5 = SecurityDialogs.showPartiallySignedWarningDialog(crtJnlpF(), null, null); //Assert.assertEquals(r.ea, r5); - NamePassword r6 = SecurityDialogs.showAuthenticationPrompt(null, 123456, null, null); + NamePassword r6 = Dialogs.showAuthenticationPrompt(null, 123456, null, null); Assert.assertEquals(r.np, r6); - boolean r7 = SecurityDialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); + boolean r7 = Dialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); Assert.assertEquals(r.b, r7); - boolean r8 = SecurityDialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); + boolean r8 = Dialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); Assert.assertEquals(r.b, r8); - boolean r9 = SecurityDialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); + boolean r9 = Dialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); Assert.assertEquals(r.b, r9); } private void testAllDialogsNullResults() throws MalformedURLException { //anything but shortcut - AccessWarningPaneComplexReturn r1 = SecurityDialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); + AccessWarningPaneComplexReturn r1 = Dialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); Assert.assertEquals(null, r1); //shortcut - AccessWarningPaneComplexReturn r2 = SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); + AccessWarningPaneComplexReturn r2 = Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); Assert.assertEquals(null, r2); - YesNo r3 = SecurityDialogs.showUnsignedWarningDialog(crtJnlpF()); - Assert.assertEquals(null, r3); //can't emulate security delegate now //YesNoSandbox r4 = SecurityDialogs.showCertWarningDialog(SecurityDialogs.AccessType.UNVERIFIED, crtJnlpF(), null, null); //Assert.assertEquals(r.p, r4.getValue()); //YesNo r5 = SecurityDialogs.showPartiallySignedWarningDialog(crtJnlpF(), null, null); //Assert.assertEquals(r.ea, r5); - NamePassword r6 = SecurityDialogs.showAuthenticationPrompt(null, 123456, null, null); + NamePassword r6 = Dialogs.showAuthenticationPrompt(null, 123456, null, null); Assert.assertEquals(null, r6); - boolean r7 = SecurityDialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); + boolean r7 = Dialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); Assert.assertEquals(false, r7); - boolean r8 = SecurityDialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); + boolean r8 = Dialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); Assert.assertEquals(false, r8); - boolean r9 = SecurityDialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); + boolean r9 = Dialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); Assert.assertEquals(false, r9); } - private void checkUnsignedActing(Boolean b) throws MalformedURLException { - checkUnsignedActing(b, b); - } - /* * testPartiallySignedBehaviour(); needs security delegate to set sandbox, so sometimes results are strange */ - private void checkUnsignedActing(Boolean b1, Boolean b2) throws MalformedURLException { - if (b1 != null) { - boolean r10 = testUnsignedBehaviour(); - Assert.assertEquals(b1.booleanValue(), r10); - } + private void checkUnsignedActing(Boolean b2) throws MalformedURLException { if (b2 != null) { boolean r11 = testPartiallySignedBehaviour(); Assert.assertEquals(b2.booleanValue(), r11); } } - private boolean testUnsignedBehaviour() throws MalformedURLException { - try { - UnsignedAppletTrustConfirmation.checkUnsignedWithUserIfRequired(crtJnlpF()); - return true; - } catch (LaunchException ex) { - return false; - } - } - private boolean testPartiallySignedBehaviour() throws MalformedURLException { try { UnsignedAppletTrustConfirmation.checkPartiallySignedWithUserIfRequired(null, crtJnlpF(), null); @@ -398,56 +378,50 @@ private void countNPES(int allowedRuns) throws MalformedURLException { try { metcounter++; //anything but shortcut - SecurityDialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); + Dialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; //shortcut - SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); + Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showUnsignedWarningDialog(crtJnlpF()); + Dialogs.showCertWarningDialog(AccessType.UNVERIFIED, crtJnlpF(), null, null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showCertWarningDialog(AccessType.UNVERIFIED, crtJnlpF(), null, null); + Dialogs.showPartiallySignedWarningDialog(crtJnlpF(), null, null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showPartiallySignedWarningDialog(crtJnlpF(), null, null); + Dialogs.showAuthenticationPrompt(null, 123456, null, null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showAuthenticationPrompt(null, 123456, null, null); + Dialogs.showMissingALACAttributePanel(crtJnlpF(), null, null); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showMissingALACAttributePanel(crtJnlpF(), null, null); + Dialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); } catch (NullPointerException ex) { npecounter++; } try { metcounter++; - SecurityDialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); - } catch (NullPointerException ex) { - npecounter++; - } - try { - metcounter++; - SecurityDialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); + Dialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); } catch (NullPointerException ex) { npecounter++; } @@ -465,7 +439,6 @@ private void checkUnsignedNPE(Boolean b1, Boolean b2) throws MalformedURLExcepti int metcounter = 0; int maxcount = 0; boolean ex1 = false; - boolean ex2 = false; if (b1 != null) { maxcount++; try { @@ -475,22 +448,10 @@ private void checkUnsignedNPE(Boolean b1, Boolean b2) throws MalformedURLExcepti ex1 = true; } } - if (b2 != null) { - maxcount++; - try { - metcounter++; - testUnsignedBehaviour(); - } catch (NullPointerException ex) { - ex2 = true; - } - } Assert.assertEquals(maxcount, metcounter); if (b1 != null) { Assert.assertEquals(b1.booleanValue(), ex1); } - if (b2 != null) { - Assert.assertEquals(b2.booleanValue(), ex2); - } } @Test(timeout = 10000)//if gui pops up @@ -525,11 +486,11 @@ public void testUnsignedDialogsNotHeadlessTrustAllPrompt() throws Exception { setAS(AppletSecurityLevel.ASK_UNSIGNED); try { fakeQueue(); - checkUnsignedActing(true, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_ALL); - checkUnsignedActing(false, null); + checkUnsignedActing(null); setAS(AppletSecurityLevel.DENY_UNSIGNED); - checkUnsignedActing(false, null); + checkUnsignedActing(null); } finally { resetQueue(); } @@ -542,8 +503,6 @@ public void testUnsignedDialogsNotHeadlessTrustNonePrompt() throws Exception { JNLPRuntime.setTrustNone(true); setPrompt(true); //ignored setAS(AppletSecurityLevel.ALLOW_UNSIGNED); - boolean r10 = testUnsignedBehaviour(); - Assert.assertEquals(true, r10); checkUnsignedNPE(false); setAS(AppletSecurityLevel.ASK_UNSIGNED); try { @@ -551,47 +510,14 @@ public void testUnsignedDialogsNotHeadlessTrustNonePrompt() throws Exception { // Assert.assertEquals(false, r11); checkUnsignedNPE(true); setAS(AppletSecurityLevel.DENY_ALL); - boolean r12 = testUnsignedBehaviour(); - Assert.assertEquals(false, r12); checkUnsignedNPE(true, false); setAS(AppletSecurityLevel.DENY_UNSIGNED); - boolean r13 = testUnsignedBehaviour(); - Assert.assertEquals(false, r13); checkUnsignedNPE(true, false); } finally { resetQueue(); } } - @Test(timeout = 10000)//if gui pops up - public void testUnsignedDialogsNotHeadlessTrustNoneTrustAllPrompt() throws Exception { - JNLPRuntime.setHeadless(false); - JNLPRuntime.setTrustAll(true); - JNLPRuntime.setTrustNone(true); - setPrompt(true); //ignored - setAS(AppletSecurityLevel.ALLOW_UNSIGNED); - boolean a = testUnsignedBehaviour(); - Assert.assertTrue(a); - checkUnsignedNPE(false); - setAS(AppletSecurityLevel.ASK_UNSIGNED); - try { - fakeQueue(); - boolean r10 = testUnsignedBehaviour(); - Assert.assertEquals(false, r10); - checkUnsignedNPE(null, false); - setAS(AppletSecurityLevel.DENY_ALL); - boolean r11 = testUnsignedBehaviour(); - Assert.assertEquals(false, r11); - checkUnsignedNPE(null, false); - setAS(AppletSecurityLevel.DENY_UNSIGNED); - boolean r12 = testUnsignedBehaviour(); - Assert.assertEquals(false, r12); - checkUnsignedNPE(null, false); - } finally { - resetQueue(); - } - } - @Test(timeout = 10000)//if gui pops up public void testUnsignedDialogsNotHeadlessPrompt() throws Exception { JNLPRuntime.setHeadless(false); @@ -603,12 +529,8 @@ public void testUnsignedDialogsNotHeadlessPrompt() throws Exception { setAS(AppletSecurityLevel.ASK_UNSIGNED); checkUnsignedNPE(true, true); setAS(AppletSecurityLevel.DENY_ALL); - boolean r11 = testUnsignedBehaviour(); - Assert.assertEquals(false, r11); checkUnsignedNPE(true, false); setAS(AppletSecurityLevel.DENY_UNSIGNED); - boolean r12 = testUnsignedBehaviour(); - Assert.assertEquals(false, r12); checkUnsignedNPE(true, false); } @@ -628,20 +550,18 @@ public void testUnsignedDialogsNotHeadlessPrompt() throws Exception { + ".* \\Q" + urlstr + "\\E "; private void runRememeberableClasses(ExpectedResults r) throws MalformedURLException { - boolean r7 = SecurityDialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); + boolean r7 = Dialogs.showMissingALACAttributePanel(crtJnlpF(), null, new HashSet()); Assert.assertEquals(r.b, r7); - boolean r8 = SecurityDialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); + boolean r8 = Dialogs.showMatchingALACAttributePanel(crtJnlpF(), url, new HashSet()); Assert.assertEquals(r.b, r8); - boolean r9 = testUnsignedBehaviour(); - Assert.assertEquals(r.b, r9); //skipping this one, ahrd to mock certVerifier // boolean r5 = testPartiallySignedBehaviour(); //Assert.assertEquals(r.b, r5); - boolean r6 = SecurityDialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); + boolean r6 = Dialogs.showMissingPermissionsAttributeDialogue(crtJnlpF()); Assert.assertEquals(r.b, r6); - AccessWarningPaneComplexReturn r1 = SecurityDialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); + AccessWarningPaneComplexReturn r1 = Dialogs.showAccessWarningDialog(AccessType.PRINTER, crtJnlpF(), null); Assert.assertEquals(r.p, r1.getRegularReturn().getValue()); - AccessWarningPaneComplexReturn r2 = SecurityDialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); + AccessWarningPaneComplexReturn r2 = Dialogs.showAccessWarningDialog(AccessType.CREATE_DESKTOP_SHORTCUT, crtJnlpF(), null); Assert.assertEquals(r.p, r2.getRegularReturn().getValue()); } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanelTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanelTest.java index 791d2427b..1c9e50474 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanelTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/apptrustwarningpanel/AppTrustWarningPanelTest.java @@ -1,8 +1,7 @@ package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.apptrustwarningpanel; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogPanel; -import net.sourceforge.jnlp.PluginParameters; import net.adoptopenjdk.icedteaweb.testing.browsertesting.browsers.firefox.FirefoxProfilesOperator; +import net.sourceforge.jnlp.PluginParameters; import net.sourceforge.jnlp.config.PathsAndFiles; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -16,6 +15,7 @@ import java.util.List; import java.util.Map; +import static net.adoptopenjdk.icedteaweb.ui.swing.SwingUtils.htmlWrap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -120,7 +120,7 @@ public void testGetQuestionPanelKey() throws Exception { public void testHtmlWrap() throws Exception { final String testText = "This is some text"; final String expectedResult = "This is some text"; - final String actualResult = SecurityDialogPanel.htmlWrap(testText); + final String actualResult = htmlWrap(testText); assertEquals("htmlWrap should properly wrap text with HTML tags", expectedResult, actualResult); } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/splashscreen/parts/InformationElementTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/splashscreen/parts/InformationElementTest.java index 3ce634592..9d342070c 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/splashscreen/parts/InformationElementTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/parts/splashscreen/parts/InformationElementTest.java @@ -44,6 +44,7 @@ import org.junit.Test; import java.io.ByteArrayInputStream; +import java.net.MalformedURLException; import static java.nio.charset.StandardCharsets.UTF_8; import static net.adoptopenjdk.icedteaweb.jnlp.element.information.HomepageDesc.HOMEPAGE_ELEMENT; @@ -224,35 +225,35 @@ public void getHeaderTest() { } @Test - public void createFromJNLP() throws ParseException { + public void createFromJNLP() throws ParseException, MalformedURLException { final ParserSettings parser = new ParserSettings(); final InformationElement ie0 = InformationElement.createFromJNLP(null); assertNotNull(ie0); //title, vendor and homepage are obligatory.. not so much to test final String exJnlp2 = testJnlpHeader + title.toXml() + "\n" + homepage.toXml() + "\n" + vendor.toXml() + "\n" + testJnlpFooter; - final JNLPFile jnlpFile2 = new JNLPFile(new ByteArrayInputStream(exJnlp2.getBytes(UTF_8)), parser); + final JNLPFile jnlpFile2 = new JNLPFile(new ByteArrayInputStream(exJnlp2.getBytes(UTF_8)), null,null, parser, null); final InformationElement ie2 = InformationElement.createFromJNLP(jnlpFile2); assertNotNull(ie2); assertEquals(3, ie2.getHeader().size()); assertEquals(0, ie2.getDescriptions().size()); final String exJnlp3 = testJnlpHeader + title.toXml() + "\n" + homepage.toXml() + "\n" + vendor.toXml() + "\n" + toolTipD.toXml() + "\n" + testJnlpFooter; - final JNLPFile jnlpFile3 = new JNLPFile(new ByteArrayInputStream(exJnlp3.getBytes(UTF_8)), parser); + final JNLPFile jnlpFile3 = new JNLPFile(new ByteArrayInputStream(exJnlp3.getBytes(UTF_8)), null, null, parser, null); final InformationElement ie3 = InformationElement.createFromJNLP(jnlpFile3); assertNotNull(ie3); assertEquals(3, ie3.getHeader().size()); assertEquals(1, ie3.getDescriptions().size()); final String exJnlp4 = testJnlpHeader + title.toXml() + "\n" + homepage.toXml() + "\n" + vendor.toXml() + "\n" + noKindD.toXml() + "\n" + testJnlpFooter; - final JNLPFile jnlpFile4 = new JNLPFile(new ByteArrayInputStream(exJnlp4.getBytes(UTF_8)), parser); + final JNLPFile jnlpFile4 = new JNLPFile(new ByteArrayInputStream(exJnlp4.getBytes(UTF_8)), null, null, parser, null); final InformationElement ie4 = InformationElement.createFromJNLP(jnlpFile4); assertNotNull(ie4); assertEquals(3, ie4.getHeader().size()); assertEquals(1, ie4.getDescriptions().size()); final String exJnlp5 = testJnlpHeader + title.toXml() + "\n" + homepage.toXml() + "\n" + vendor.toXml() + "\n" + noKindD.toXml() + "\n" + toolTipD.toXml() + "\n" + testJnlpFooter; - final JNLPFile jnlpFile5 = new JNLPFile(new ByteArrayInputStream(exJnlp5.getBytes(UTF_8)), parser); + final JNLPFile jnlpFile5 = new JNLPFile(new ByteArrayInputStream(exJnlp5.getBytes(UTF_8)), null, null, parser, null); final InformationElement ie5 = InformationElement.createFromJNLP(jnlpFile5); assertNotNull(ie5); assertEquals(3, ie5.getHeader().size()); diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtilTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtilTest.java new file mode 100644 index 000000000..a136afed8 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/client/util/html/HtmlUtilTest.java @@ -0,0 +1,33 @@ +package net.adoptopenjdk.icedteaweb.client.util.html; + +import org.junit.Assert; +import org.junit.Test; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +public class HtmlUtilTest { + + @Test + public void testListOfFiveLinksWithMaxDisplayedThree() throws MalformedURLException { + final List namedUrls = Arrays.asList( + NamedUrl.of("first", new URL("http://localhost")), + NamedUrl.of("first", new URL("http://localhost")), + NamedUrl.of("first", new URL("http://localhost")), + NamedUrl.of("first", new URL("http://localhost")), + NamedUrl.of("first", new URL("http://localhost")) + ); + + + final String listOfThreeLinks = HtmlUtil.unorderedListOf(namedUrls, 3); + + Assert.assertEquals("

    ", listOfThreeLinks); + } +} \ No newline at end of file diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResourcesTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResourcesTest.java new file mode 100644 index 000000000..45dfe79ff --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/JNLPResourcesTest.java @@ -0,0 +1,95 @@ +package net.adoptopenjdk.icedteaweb.jnlp.element.resource; + +import net.sourceforge.jnlp.JNLPFile; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Locale; +import java.util.Map; + +import static java.util.Arrays.asList; +import static java.util.Locale.ENGLISH; +import static java.util.Locale.GERMAN; + +/** + * ... + */ +public class JNLPResourcesTest { + + @Test + public void testFilterOs0arch0english() { + JNLPResources filtered = getResources().filterResources(ENGLISH, "os0", "arch0"); + final Map properties = filtered.getPropertiesMap(); + Assert.assertEquals("general", properties.get("general")); + Assert.assertEquals("general", properties.get("os")); + Assert.assertEquals("general", properties.get("arch")); + Assert.assertEquals("general", properties.get("locale")); + } + + @Test + public void testFilterOs1arch0english() { + JNLPResources filtered = getResources().filterResources(ENGLISH, "os1", "arch0"); + final Map properties = filtered.getPropertiesMap(); + Assert.assertEquals("general", properties.get("general")); + Assert.assertEquals("os1", properties.get("os")); + Assert.assertEquals("general", properties.get("arch")); + Assert.assertEquals("general", properties.get("locale")); + } + + @Test + public void testFilterOs1arch1english() { + JNLPResources filtered = getResources().filterResources(ENGLISH, "os1", "arch1"); + final Map properties = filtered.getPropertiesMap(); + Assert.assertEquals("general", properties.get("general")); + Assert.assertEquals("os1", properties.get("os")); + Assert.assertEquals("arch1", properties.get("arch")); + Assert.assertEquals("general", properties.get("locale")); + } + + @Test + public void testFilterOs2arch2german() { + JNLPResources filtered = getResources().filterResources(GERMAN, "os2", "arch2"); + final Map properties = filtered.getPropertiesMap(); + Assert.assertEquals("general", properties.get("general")); + Assert.assertEquals("os2", properties.get("os")); + Assert.assertEquals("arch2", properties.get("arch")); + Assert.assertEquals("german", properties.get("locale")); + } + + private JNLPResources getResources() { + final JNLPFile jnlpFile = null; + + final ResourcesDesc general = new ResourcesDesc(jnlpFile, locale(), os(), arch()); + general.addResource(new PropertyDesc("general", "general")); + general.addResource(new PropertyDesc("os", "general")); + general.addResource(new PropertyDesc("arch", "general")); + general.addResource(new PropertyDesc("locale", "general")); + + final ResourcesDesc os1 = new ResourcesDesc(jnlpFile, locale(), os("os1"), arch()); + os1.addResource(new PropertyDesc("os", "os1")); + + final ResourcesDesc os1arch1 = new ResourcesDesc(jnlpFile, locale(), os("os1"), arch("arch1")); + os1arch1.addResource(new PropertyDesc("arch", "arch1")); + + final ResourcesDesc os2arch2 = new ResourcesDesc(jnlpFile, locale(), os("os2"), arch("arch2")); + os2arch2.addResource(new PropertyDesc("os", "os2")); + os2arch2.addResource(new PropertyDesc("arch", "arch2")); + + final ResourcesDesc german = new ResourcesDesc(jnlpFile, locale(GERMAN), os(), arch()); + german.addResource(new PropertyDesc("locale", "german")); + + return new JNLPResources(asList(general, os1, os1arch1, os2arch2, german)); + } + + private Locale[] locale(Locale... locale) { + return locale; + } + + private String[] os(String... os) { + return os; + } + + private String[] arch(String... arch) { + return arch; + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDescTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDescTest.java new file mode 100644 index 000000000..64a54bd5b --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/resource/PackageDescTest.java @@ -0,0 +1,75 @@ +package net.adoptopenjdk.icedteaweb.jnlp.element.resource; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * ... + */ +public class PackageDescTest { + + private static final String PART = "part1"; + private static final boolean RECURSIVE = true; + private static final boolean NOT_RECURSIVE = false; + + private static final String ROOT_PACKAGE = "foo."; + private static final String PACKAGE1 = ROOT_PACKAGE + "bar.bar."; + private static final String PACKAGE2 = ROOT_PACKAGE + "ele.fant."; + private static final String CLASS1 = PACKAGE1 + "SomeClass"; + private static final String CLASS2 = PACKAGE1 + "OtherClass"; + private static final String CLASS3 = PACKAGE2 + "SomeClass"; + private static final String CLASS4 = PACKAGE2 + "OtherClass"; + private static final String SUFFIX = "*"; + + @Test + public void testExactClassName() { + final PackageDesc packageDesc = new PackageDesc(CLASS1, PART, NOT_RECURSIVE); + + assertTrue(packageDesc.matches(CLASS1)); + assertFalse(packageDesc.matches(CLASS2)); + assertFalse(packageDesc.matches(CLASS3)); + assertFalse(packageDesc.matches(CLASS4)); + } + + @Test + public void testPackageNameNotRecursive() { + final PackageDesc packageDesc = new PackageDesc(PACKAGE1 + SUFFIX, PART, NOT_RECURSIVE); + + assertTrue(packageDesc.matches(CLASS1)); + assertTrue(packageDesc.matches(CLASS2)); + assertFalse(packageDesc.matches(CLASS3)); + assertFalse(packageDesc.matches(CLASS4)); + } + + @Test + public void testPackageNameRecursive() { + final PackageDesc packageDesc = new PackageDesc(PACKAGE1 + SUFFIX, PART, RECURSIVE); + + assertTrue(packageDesc.matches(CLASS1)); + assertTrue(packageDesc.matches(CLASS2)); + assertFalse(packageDesc.matches(CLASS3)); + assertFalse(packageDesc.matches(CLASS4)); + } + + @Test + public void testRootPackageNameNotRecursive() { + final PackageDesc packageDesc = new PackageDesc(ROOT_PACKAGE + SUFFIX, PART, NOT_RECURSIVE); + + assertFalse(packageDesc.matches(CLASS1)); + assertFalse(packageDesc.matches(CLASS2)); + assertFalse(packageDesc.matches(CLASS3)); + assertFalse(packageDesc.matches(CLASS4)); + } + + @Test + public void testRootPackageNameRecursive() { + final PackageDesc packageDesc = new PackageDesc(ROOT_PACKAGE + SUFFIX, PART, RECURSIVE); + + assertTrue(packageDesc.matches(CLASS1)); + assertTrue(packageDesc.matches(CLASS2)); + assertTrue(packageDesc.matches(CLASS3)); + assertTrue(packageDesc.matches(CLASS4)); + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesCheckerTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesCheckerTest.java index 6c0a8ac25..4da8c371a 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesCheckerTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/manifest/ManifestAttributesCheckerTest.java @@ -33,35 +33,23 @@ */ package net.adoptopenjdk.icedteaweb.manifest; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFileWithJar; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.config.ConfigurationConstants; import net.sourceforge.jnlp.runtime.DummySecurityDelegate; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import org.junit.Assert; +import net.sourceforge.jnlp.runtime.SecurityDelegate; import org.junit.Test; import java.net.MalformedURLException; import java.net.URL; -public class ManifestAttributesCheckerTest { +import static net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker.MANIFEST_ATTRIBUTES_CHECK.ALAC; +import static net.sourceforge.jnlp.config.ConfigurationConstants.KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK; - @Test - public void stripDocbaseTest() throws Exception { - tryTest("http://aaa.bb/ccc/file.html", "http://aaa.bb/ccc/"); - tryTest("http://aaa.bb/ccc/file.html/", "http://aaa.bb/ccc/file.html/"); - tryTest("http://aaa.bb/ccc/dir/", "http://aaa.bb/ccc/dir/"); - tryTest("http://aaa.bb/ccc/dir", "http://aaa.bb/ccc/"); - tryTest("http://aaa.bb/ccc/", "http://aaa.bb/ccc/"); - tryTest("http://aaa.bb/ccc", "http://aaa.bb/"); - tryTest("http://aaa.bb/", "http://aaa.bb/"); - tryTest("http://aaa.bb", "http://aaa.bb"); - } +public class ManifestAttributesCheckerTest { @Test public void checkAllCheckAlacTest() throws LaunchException, MalformedURLException { @@ -70,18 +58,11 @@ public void checkAllCheckAlacTest() throws LaunchException, MalformedURLExceptio URL jar1 = new URL("http://aaa/bb/a.jar"); URL jar2 = new URL("http://aaa/bb/lib/a.jar"); JNLPFile file = new DummyJNLPFileWithJar(codebase, jar1, jar2); - SecurityDesc security = new SecurityDesc(file, AppletPermissionLevel.ALL,SecurityDesc.ALL_PERMISSIONS, codebase); + SecurityDesc security = new SecurityDesc(ApplicationEnvironment.ALL); SecurityDelegate securityDelegate = new DummySecurityDelegate(); - ManifestAttributesChecker checker = new ManifestAttributesChecker(security, file, JNLPClassLoader.SigningState.FULL, securityDelegate); - JNLPRuntime.getConfiguration().setProperty(ConfigurationConstants.KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK, ManifestAttributesChecker.MANIFEST_ATTRIBUTES_CHECK.ALAC.name()); + ManifestAttributesChecker checker = new ManifestAttributesChecker(file, true, null); + JNLPRuntime.getConfiguration().setProperty(KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK, ALAC.name()); checker.checkAll(); } - private static void tryTest(String src, String expected) throws MalformedURLException { - URL s = new URL(src); - URL q = ManifestAttributesChecker.stripDocbase(s); - //junit is failing for me on url.equals(url)... - Assert.assertEquals(expected, q.toExternalForm()); - } - } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerTest.java similarity index 98% rename from core/src/test/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerTest.java rename to core/src/test/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerTest.java index 2d51f903b..e1b2725a8 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/resources/ResourceTrackerTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/resources/DefaultResourceTrackerTest.java @@ -63,7 +63,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class ResourceTrackerTest extends NoStdOutErrTest { +public class DefaultResourceTrackerTest extends NoStdOutErrTest { public static ServerLauncher downloadServer; private static ByteArrayOutputStream currentErrorStream; @@ -180,7 +180,7 @@ public void testDownloadResource() throws IOException { URL url = downloadServer.getUrl("resource"); - ResourceTracker rt = new ResourceTracker(); + ResourceTracker rt = new DefaultResourceTracker(); rt.addResource(url, (VersionString) null, UpdatePolicy.FORCE); File downloadFile = rt.getCacheFile(url); diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDescTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/security/PermissionsManagerTest.java similarity index 71% rename from core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDescTest.java rename to core/src/test/java/net/adoptopenjdk/icedteaweb/security/PermissionsManagerTest.java index 91bd0dace..72f00ffc8 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/jnlp/element/security/SecurityDescTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/security/PermissionsManagerTest.java @@ -31,38 +31,33 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ -package net.adoptopenjdk.icedteaweb.jnlp.element.security; +package net.adoptopenjdk.icedteaweb.security; -import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFile; import org.junit.Test; +import java.awt.AWTPermission; import java.net.URI; +import java.security.PermissionCollection; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -public class SecurityDescTest { - +public class PermissionsManagerTest { @Test public void testNotNullJnlpFile() throws Exception { Throwable t = null; try { - new SecurityDesc(new DummyJNLPFile(), AppletPermissionLevel.NONE, SecurityDesc.SANDBOX_PERMISSIONS, null); + new PermissionsManager(); } catch (Exception ex) { t = ex; } assertNull("securityDesc should not throw exception", t); } - @Test(expected = NullPointerException.class) - public void testNullJnlpFile() throws Exception { - new SecurityDesc(null, AppletPermissionLevel.NONE, SecurityDesc.SANDBOX_PERMISSIONS, null); - } - @Test public void testAppendRecursiveSubdirToCodebaseHostString() throws Exception { final String urlStr = "http://example.com"; - final String result = SecurityDesc.appendRecursiveSubdirToCodebaseHostString(urlStr); + final String result = PermissionsManager.appendRecursiveSubdirToCodebaseHostString(urlStr); final String expected = "http://example.com/-"; assertEquals(expected, result); } @@ -70,7 +65,7 @@ public void testAppendRecursiveSubdirToCodebaseHostString() throws Exception { @Test public void testAppendRecursiveSubdirToCodebaseHostString2() throws Exception { final String urlStr = "http://example.com/"; - final String result = SecurityDesc.appendRecursiveSubdirToCodebaseHostString(urlStr); + final String result = PermissionsManager.appendRecursiveSubdirToCodebaseHostString(urlStr); final String expected = "http://example.com/-"; assertEquals(expected, result); } @@ -78,7 +73,7 @@ public void testAppendRecursiveSubdirToCodebaseHostString2() throws Exception { @Test public void testAppendRecursiveSubdirToCodebaseHostString3() throws Exception { final String urlStr = "http://example.com///"; - final String result = SecurityDesc.appendRecursiveSubdirToCodebaseHostString(urlStr); + final String result = PermissionsManager.appendRecursiveSubdirToCodebaseHostString(urlStr); final String expected = "http://example.com/-"; assertEquals(expected, result); } @@ -86,122 +81,133 @@ public void testAppendRecursiveSubdirToCodebaseHostString3() throws Exception { @Test public void testAppendRecursiveSubdirToCodebaseHostStringWithPort() throws Exception { final String urlStr = "http://example.com:8080"; - final String result = SecurityDesc.appendRecursiveSubdirToCodebaseHostString(urlStr); + final String result = PermissionsManager.appendRecursiveSubdirToCodebaseHostString(urlStr); final String expected = "http://example.com:8080/-"; assertEquals(expected, result); } @Test(expected = NullPointerException.class) public void testAppendRecursiveSubdirToCodebaseHostStringWithNull() throws Exception { - SecurityDesc.appendRecursiveSubdirToCodebaseHostString(null); + PermissionsManager.appendRecursiveSubdirToCodebaseHostString(null); } @Test public void testGetHostWithSpecifiedPort() throws Exception { final URI codebase = new URI("http://example.com"); final URI expected = new URI("http://example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test public void testGetHostWithSpecifiedPortWithFtpScheme() throws Exception { final URI codebase = new URI("ftp://example.com"); final URI expected = new URI("ftp://example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test public void testGetHostWithSpecifiedPortWithUserInfo() throws Exception { final URI codebase = new URI("http://user:password@example.com"); final URI expected = new URI("http://user:password@example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test public void testGetHostWithSpecifiedPortWithPort() throws Exception { final URI codebase = new URI("http://example.com:8080"); final URI expected = new URI("http://example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test public void testGetHostWithSpecifiedPortWithPath() throws Exception { final URI codebase = new URI("http://example.com/applet/codebase/"); final URI expected = new URI("http://example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test public void testGetHostWithSpecifiedPortWithAll() throws Exception { final URI codebase = new URI("ftp://user:password@example.com:8080/applet/codebase/"); final URI expected = new URI("ftp://user:password@example.com:80"); - assertEquals(expected, SecurityDesc.getHostWithSpecifiedPort(codebase, 80)); + assertEquals(expected, PermissionsManager.getHostWithSpecifiedPort(codebase, 80)); } @Test(expected = NullPointerException.class) public void testGetHostWithSpecifiedPortWithNull() throws Exception { - SecurityDesc.getHostWithSpecifiedPort(null, 80); + PermissionsManager.getHostWithSpecifiedPort(null, 80); } @Test public void testGetHost() throws Exception { final URI codebase = new URI("http://example.com"); final URI expected = new URI("http://example.com"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test public void testGetHostWithFtpScheme() throws Exception { final URI codebase = new URI("ftp://example.com"); final URI expected = new URI("ftp://example.com"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test public void testGetHostWithUserInfo() throws Exception { final URI codebase = new URI("http://user:password@example.com"); final URI expected = new URI("http://user:password@example.com"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test public void testGetHostWithPort() throws Exception { final URI codebase = new URI("http://example.com:8080"); final URI expected = new URI("http://example.com:8080"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test public void testGetHostWithPath() throws Exception { final URI codebase = new URI("http://example.com/applet/codebase/"); final URI expected = new URI("http://example.com"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test public void testGetHostWithAll() throws Exception { final URI codebase = new URI("ftp://user:password@example.com:8080/applet/codebase/"); final URI expected = new URI("ftp://user:password@example.com:8080"); - assertEquals(expected, SecurityDesc.getHost(codebase)); + assertEquals(expected, PermissionsManager.getHost(codebase)); } @Test(expected = NullPointerException.class) public void testGetHostNull() throws Exception { - SecurityDesc.getHost(null); + PermissionsManager.getHost(null); } @Test public void testGetHostWithAppendRecursiveSubdirToCodebaseHostString() throws Exception { final URI codebase = new URI("ftp://user:password@example.com:8080/applet/codebase/"); final String expected = "ftp://user:password@example.com:8080/-"; - assertEquals(expected, SecurityDesc.appendRecursiveSubdirToCodebaseHostString(SecurityDesc.getHost(codebase).toString())); + assertEquals(expected, PermissionsManager.appendRecursiveSubdirToCodebaseHostString(PermissionsManager.getHost(codebase).toString())); } @Test public void testGetHostWithSpecifiedPortWithAppendRecursiveSubdirToCodebaseHostString() throws Exception { final URI codebase = new URI("ftp://user:password@example.com:8080/applet/codebase/"); final String expected = "ftp://user:password@example.com:80/-"; - assertEquals(expected, SecurityDesc.appendRecursiveSubdirToCodebaseHostString(SecurityDesc.getHostWithSpecifiedPort(codebase, 80).toString())); + assertEquals(expected, PermissionsManager.appendRecursiveSubdirToCodebaseHostString(PermissionsManager.getHostWithSpecifiedPort(codebase, 80).toString())); } + @Test (expected = SecurityException.class) + public void throwExpectionAsAttemptToAddPermissionToReadOnlyJ2EEPermissionCollection() { + final PermissionCollection permissions = PermissionsManager.getJ2EEPermissions(); + permissions.add(new AWTPermission("someExtraPermission")); + } + + @Test (expected = SecurityException.class) + public void throwExpectionAsAttemptToAddPermissionToReadOnlyJnlpRiaPermissionCollection() { + final PermissionCollection permissions = PermissionsManager.getJnlpRiaPermissions(); + permissions.add(new AWTPermission("someExtraPermission")); + } } diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactoryTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactoryTest.java new file mode 100644 index 000000000..755d45be0 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/security/dialog/NewDialogFactoryTest.java @@ -0,0 +1,112 @@ +package net.adoptopenjdk.icedteaweb.security.dialog; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.security.HttpsCertVerifier; +import net.sourceforge.jnlp.signing.JarCertVerifier; +import sun.security.x509.X509CertImpl; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import static net.sourceforge.jnlp.security.AccessType.CLIPBOARD_READ; +import static net.sourceforge.jnlp.security.AccessType.CLIPBOARD_WRITE; +import static net.sourceforge.jnlp.security.AccessType.CREATE_DESKTOP_SHORTCUT; +import static net.sourceforge.jnlp.security.AccessType.NETWORK; +import static net.sourceforge.jnlp.security.AccessType.PRINTER; +import static net.sourceforge.jnlp.security.AccessType.READ_FILE; +import static net.sourceforge.jnlp.security.AccessType.READ_WRITE_FILE; +import static net.sourceforge.jnlp.security.AccessType.UNVERIFIED; +import static net.sourceforge.jnlp.security.AccessType.WRITE_FILE; + +/** + * Helper class to start dialogs without starting ITW. + */ +public class NewDialogFactoryTest { + private final JNLPFile file; + private final HttpsCertVerifier httpsCertVerifier; + private final JarCertVerifier jarCertVerifier; + private final NewDialogFactory dialogFactory; + + public NewDialogFactoryTest() throws Exception { + //Locale.setDefault(Locale.GERMAN); + Locale.setDefault(Locale.ENGLISH); + JNLPRuntime.initialize(); + file = new JNLPFileFactory().create(getClass().getResource("/net/sourceforge/jnlp/basic.jnlp")); + httpsCertVerifier = new HttpsCertVerifier(new X509Certificate[]{}, true, true, "hostname"); + jarCertVerifier = new JarCertVerifier(); + dialogFactory = new NewDialogFactory(); + } + + public static void main(String[] args) throws Exception { + // new NewDialogFactoryTest().showReadWriteFileAccessWarningDialog(); + new NewDialogFactoryTest().showAccessWarningDialog(); + // new NewDialogFactoryTest().showCertWarningDialog(); + // new NewDialogFactoryTest().showMoreInfoDialog(); + // new NewDialogFactoryTest().showPartiallySignedWarningDialog(); + // new NewDialogFactoryTest().showCertInfoDialog(); + // new NewDialogFactoryTest().showMissingPermissionsAttributeDialog(); + // new NewDialogFactoryTest().showMissingALACAttributeDialog(); + // new NewDialogFactoryTest().showMatchingALACAttributeDialog(); + // new NewDialogFactoryTest().showAuthenticationPrompt(); + } + + private void showReadWriteFileAccessWarningDialog() { + dialogFactory.showAccessWarningDialog(READ_WRITE_FILE, file, new Object[]{"test"}); + } + + private void showAccessWarningDialog() { + Arrays.asList(READ_WRITE_FILE, READ_FILE, WRITE_FILE, CLIPBOARD_READ, CLIPBOARD_WRITE, PRINTER, NETWORK, CREATE_DESKTOP_SHORTCUT) + .forEach(accessType -> dialogFactory.showAccessWarningDialog(accessType, file, new Object[]{"test"})); + } + + private void showCertWarningDialog() { + dialogFactory.showCertWarningDialog(UNVERIFIED, file, jarCertVerifier, null); + dialogFactory.showCertWarningDialog(UNVERIFIED, file, httpsCertVerifier, null); + } + + private void showMoreInfoDialog() { + dialogFactory.showMoreInfoDialog(jarCertVerifier, file); + } + + private void showPartiallySignedWarningDialog() { + dialogFactory.showPartiallySignedWarningDialog(file, jarCertVerifier, null); + } + + private void showCertInfoDialog() { + dialogFactory.showCertInfoDialog(httpsCertVerifier, null); + dialogFactory.showSingleCertInfoDialog(new X509CertImpl(), null); + } + + private void showMissingPermissionsAttributeDialog() { + dialogFactory.showMissingPermissionsAttributeDialogue(file); + } + + private void showMissingALACAttributeDialog() throws MalformedURLException { + final URL codeBase = new URL("http://localhost/"); + Set remoteUrls = new HashSet<>(); + remoteUrls.add(new URL("http:/differentlocation.com/one")); + remoteUrls.add(new URL("http:/differentlocation.com/one/two")); + + dialogFactory.showMissingALACAttributePanel(file, codeBase, remoteUrls); + } + + private void showMatchingALACAttributeDialog() throws MalformedURLException { + final URL codeBase = new URL("http://localhost/"); + Set remoteUrls = new HashSet<>(); + remoteUrls.add(new URL("http:/localhost/one")); + remoteUrls.add(new URL("http:/localhost/one/two")); + + dialogFactory.showMatchingALACAttributePanel(file, codeBase, remoteUrls); + } + + private void showAuthenticationPrompt() { + dialogFactory.showAuthenticationPrompt("http://localhost/", 666, "Authentication required with pro account credentials", "Web"); + } +} diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFile.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFile.java index 6fc7fa155..8a4c97832 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFile.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFile.java @@ -34,7 +34,7 @@ package net.adoptopenjdk.icedteaweb.testing.mock; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.sourceforge.jnlp.JNLPFile; @@ -58,7 +58,7 @@ public class DummyJNLPFile extends JNLPFile { } { - this.security = new SecurityDesc(this, AppletPermissionLevel.NONE, SecurityDesc.SANDBOX_PERMISSIONS, null); + this.security = new SecurityDesc(ApplicationEnvironment.SANDBOX); } @Override diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFileWithJar.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFileWithJar.java index 3b831bb4e..e4511427d 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFileWithJar.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/testing/mock/DummyJNLPFileWithJar.java @@ -3,7 +3,7 @@ import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; import net.sourceforge.jnlp.JNLPFile; @@ -12,6 +12,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -20,7 +21,7 @@ public class DummyJNLPFileWithJar extends JNLPFile { /* Create a JARDesc for the given URL location */ private static JARDesc makeJarDesc(final URL jarLocation, final boolean main) { - return new JARDesc(jarLocation, VersionString.fromString("1"), null, false, main, false, false); + return new JARDesc(jarLocation, VersionString.fromString("1"), null, false, main, false); } private final JARDesc[] jarDescs; @@ -56,7 +57,7 @@ private DummyJNLPFileWithJar(final int main, final URL codebaseRewritter, final } infos = new ArrayList<>(); - this.security = new SecurityDesc(this, AppletPermissionLevel.NONE, SecurityDesc.SANDBOX_PERMISSIONS, null); + this.security = new SecurityDesc(ApplicationEnvironment.SANDBOX); } public URL getJarLocation() { @@ -82,8 +83,8 @@ public ResourcesDesc getResources() { } @Override - public ResourcesDesc[] getResourcesDescs() { - return new ResourcesDesc[] { getResources() }; + public List getResourcesDescs() { + return Collections.singletonList(getResources()); } @Override diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturnTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturnTest.java index 20e462343..9ac48d555 100644 --- a/core/src/test/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturnTest.java +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/ui/swing/dialogresults/AccessWarningPaneComplexReturnTest.java @@ -78,8 +78,8 @@ public void AccessWarningPaneComplexReturnTestReadWriteBad2() { @Test public void AccessWarningPaneComplexReturnTestReadWrite3() { AccessWarningPaneComplexReturn aw1 = new AccessWarningPaneComplexReturn(true); - aw1.setDesktop(new AccessWarningPaneComplexReturn.ShortcutResult(true)); - aw1.setMenu(new AccessWarningPaneComplexReturn.ShortcutResult(false)); + aw1.setDesktop(new ShortcutResult(true)); + aw1.setMenu(new ShortcutResult(false)); String s1 = aw1.writeValue(); AccessWarningPaneComplexReturn aw11 = AccessWarningPaneComplexReturn.readValue(s1); Assert.assertEquals(aw1.getRegularReturn().getValue(), aw11.getRegularReturn().getValue()); @@ -94,8 +94,8 @@ public void AccessWarningPaneComplexReturnTestReadWrite3() { @Test public void AccessWarningPaneComplexReturnTestReadWrite4() { AccessWarningPaneComplexReturn aw1 = new AccessWarningPaneComplexReturn(true); - aw1.setDesktop(new AccessWarningPaneComplexReturn.ShortcutResult("b1", true, AccessWarningPaneComplexReturn.Shortcut.BROWSER, false)); - aw1.setMenu(new AccessWarningPaneComplexReturn.ShortcutResult("b2",false, AccessWarningPaneComplexReturn.Shortcut.JAVAWS_HTML, true)); + aw1.setDesktop(new ShortcutResult("b1", true, AccessWarningPaneComplexReturn.Shortcut.BROWSER, false)); + aw1.setMenu(new ShortcutResult("b2",false, AccessWarningPaneComplexReturn.Shortcut.JAVAWS_HTML, true)); String s1 = aw1.writeValue(); AccessWarningPaneComplexReturn aw11 = AccessWarningPaneComplexReturn.readValue(s1); Assert.assertEquals(aw1.getRegularReturn().getValue(), aw11.getRegularReturn().getValue()); diff --git a/core/src/test/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStoreTest.java b/core/src/test/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStoreTest.java new file mode 100644 index 000000000..2a80455f0 --- /dev/null +++ b/core/src/test/java/net/adoptopenjdk/icedteaweb/userdecision/UserDecisionsFileStoreTest.java @@ -0,0 +1,146 @@ +package net.adoptopenjdk.icedteaweb.userdecision; + +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.adoptopenjdk.icedteaweb.userdecision.UserDecision.Key; +import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny.ALLOW; +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny.DENY; +import static net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDenySandbox.SANDBOX; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link UserDecisionsFileStore}. + */ +public class UserDecisionsFileStoreTest { + + private static final Key decisionKey = Key.RUN_UNSIGNED_APPLICATION; + private static final Key otherKey1 = Key.RUN_PARTIALLY_APPLICATION; + private static final Key otherKey2 = Key.RUN_MISSING_ALAC_APPLICATION; + private static final Key otherKey3 = Key.RUN_MISSING_PERMISSIONS_APPLICATION; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private UserDecisions userDecisions; + private File store; + + @Before + public final void setUp() throws Exception { + store = temporaryFolder.newFile("userDecisionStore.json"); + userDecisions = new UserDecisionsFileStore(store); + } + + @Test + public void newStoreShouldReturnOptionalEmpty() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + + // when + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.empty())); + } + + @Test + public void shouldSaveDecisionForApplication() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + + // when + userDecisions.saveForApplication(file, UserDecision.of(decisionKey, DENY)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.of(DENY))); + } + + @Test + public void shouldSaveDecisionForDomain() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + + // when + userDecisions.saveForDomain(file, UserDecision.of(decisionKey, DENY)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.of(DENY))); + } + + @Test + public void shouldCreateFileIfMissing() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + assertTrue(store.delete()); + + // when + userDecisions.saveForApplication(file, UserDecision.of(decisionKey, DENY)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.of(DENY))); + } + + @Test + public void applicationShouldTakePrecedenceOverDomain() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + + // when + // save domain twice to ensure order does not matter + userDecisions.saveForDomain(file, UserDecision.of(decisionKey, ALLOW)); + userDecisions.saveForApplication(file, UserDecision.of(decisionKey, DENY)); + userDecisions.saveForDomain(file, UserDecision.of(decisionKey, ALLOW)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.of(DENY))); + } + + @Test + public void shouldNotReturnSavedValueOfOtherKey() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + + // when + userDecisions.saveForApplication(file, UserDecision.of(otherKey1, DENY)); + userDecisions.saveForApplication(file, UserDecision.of(otherKey2, ALLOW)); + userDecisions.saveForApplication(file, UserDecision.of(otherKey3, SANDBOX)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.empty())); + } + + @Test + public void shouldNotReturnSavedValueOfOtherApplication() throws Exception { + // given + final JNLPFile file = loadJnlpFile("/net/sourceforge/jnlp/basic.jnlp"); + final JNLPFile otherFile = loadJnlpFile("/net/sourceforge/jnlp/launchApp.jnlp"); + + // when + userDecisions.saveForApplication(otherFile, UserDecision.of(decisionKey, DENY)); + final Optional result = userDecisions.getUserDecisions(decisionKey, file, AllowDeny.class); + + // then + assertThat(result, is(Optional.empty())); + } + + private JNLPFile loadJnlpFile(String name) throws IOException, ParseException { + return new JNLPFileFactory().create(getClass().getResource(name)); + } +} diff --git a/core/src/test/java/net/sourceforge/jnlp/JNLPFileTest.java b/core/src/test/java/net/sourceforge/jnlp/JNLPFileTest.java index 4b3bd15eb..d6eb2f9ad 100644 --- a/core/src/test/java/net/sourceforge/jnlp/JNLPFileTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/JNLPFileTest.java @@ -35,8 +35,7 @@ package net.sourceforge.jnlp; import net.adoptopenjdk.icedteaweb.JavaSystemProperties; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationPermissionLevel; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.testing.annotations.Bug; import net.adoptopenjdk.icedteaweb.testing.mock.MockJNLPFile; import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; @@ -50,7 +49,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; -import java.util.Map; public class JNLPFileTest extends NoStdOutErrTest{ Locale jvmLocale = new Locale("en", "CA", "utf8"); @@ -90,7 +88,7 @@ public void testCodebaseConstructorWithInputstreamAndCodebase() throws Exception InputStream is = new ByteArrayInputStream(jnlpContext.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false,false,false)); + JNLPFile jnlpFile = new JNLPFile(is, null, null, new ParserSettings(false,false,false), null); Assert.assertEquals("http://icedtea.classpath.org/", jnlpFile.getCodeBase().toExternalForm()); Assert.assertEquals("redhat.embeddedjnlp", jnlpFile.getApplet().getMainClass()); @@ -98,65 +96,6 @@ public void testCodebaseConstructorWithInputstreamAndCodebase() throws Exception Assert.assertEquals(2, jnlpFile.getResources().getJARs().length); } - @Test - public void testPropertyRestrictions() throws MalformedURLException, ParseException { - String jnlpContents = "\n" + - "\n" + - " \n" + - " Parsing Test\n" + - " IcedTea\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " " + - " \n" + - " \n" + - " " + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - - URL codeBase = new URL("http://www.redhat.com/"); - InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false,false,false)); - - ResourcesDesc resources; - Map properties; - - resources = jnlpFile.getResources(Locale.getDefault(), "os0", "arch0"); - properties = resources.getPropertiesMap(); - Assert.assertEquals("general", properties.get("general")); - Assert.assertEquals("general", properties.get("os")); - Assert.assertEquals("general", properties.get("arch")); - - resources = jnlpFile.getResources(Locale.getDefault(), "os1", "arch0"); - properties = resources.getPropertiesMap(); - Assert.assertEquals("general", properties.get("general")); - Assert.assertEquals("os1", properties.get("os")); - Assert.assertEquals("general", properties.get("arch")); - - resources = jnlpFile.getResources(Locale.getDefault(), "os1", "arch1"); - properties = resources.getPropertiesMap(); - Assert.assertEquals("general", properties.get("general")); - Assert.assertEquals("os1", properties.get("os")); - Assert.assertEquals("arch1", properties.get("arch")); - - resources = jnlpFile.getResources(Locale.getDefault(), "os2", "arch2"); - properties = resources.getPropertiesMap(); - Assert.assertEquals("general", properties.get("general")); - Assert.assertEquals("os2", properties.get("os")); - Assert.assertEquals("arch2", properties.get("arch")); - } - @Bug(id={"PR1533"}) @Test public void testDownloadOptionsAppliedEverywhere() throws MalformedURLException, ParseException { @@ -180,12 +119,15 @@ public void testDownloadOptionsAppliedEverywhere() throws MalformedURLException, " " + " " + " \n" + + " " + + " " + + " \n" + " \n" + ""; URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false,false,false)); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false,false,false), null); DownloadOptions downloadOptions = jnlpFile.getDownloadOptions(); Assert.assertTrue(downloadOptions.useExplicitPack()); @@ -217,7 +159,7 @@ public void testDownloadOptionsFilteredOut() throws MalformedURLException, Parse URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false,false,false)); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false,false,false), null); DownloadOptions downloadOptions = jnlpFile.getDownloadOptions(); Assert.assertFalse(downloadOptions.useExplicitPack()); @@ -237,52 +179,52 @@ public void testDownloadOptionsFilteredOut() throws MalformedURLException, Parse + ""; @Test - public void testGetRequestedPermissionLevel1() throws MalformedURLException, ParseException { + public void testGetRequestedEnvironment1() throws MalformedURLException, ParseException { String jnlpContents = minimalJnlp.replace("SECURITY", ""); URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false, false, false)); - Assert.assertEquals(ApplicationPermissionLevel.NONE, jnlpFile.getApplicationPermissionLevel()); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false, false, false), null); + Assert.assertEquals(ApplicationEnvironment.SANDBOX, jnlpFile.getApplicationEnvironment()); } @Test - public void testGetRequestedPermissionLevel2() throws MalformedURLException, ParseException { - String jnlpContents = minimalJnlp.replace("SECURITY", "<"+ ApplicationPermissionLevel.ALL.getValue()+"/>"); + public void testGetRequestedEnvironment2() throws MalformedURLException, ParseException { + String jnlpContents = minimalJnlp.replace("SECURITY", "<"+ ApplicationEnvironment.ALL.getValue()+"/>"); URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false, false, false)); - Assert.assertEquals(ApplicationPermissionLevel.ALL, jnlpFile.getApplicationPermissionLevel()); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false, false, false), null); + Assert.assertEquals(ApplicationEnvironment.ALL, jnlpFile.getApplicationEnvironment()); } @Test - public void testGetRequestedPermissionLevel3() throws MalformedURLException, ParseException { + public void testGetRequestedEnvironment3() throws MalformedURLException, ParseException { String jnlpContents = minimalJnlp.replace("SECURITY", ""); URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false, false, false)); - Assert.assertEquals(ApplicationPermissionLevel.NONE, jnlpFile.getApplicationPermissionLevel()); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false, false, false), null); + Assert.assertEquals(ApplicationEnvironment.SANDBOX, jnlpFile.getApplicationEnvironment()); } @Test - public void testGetRequestedPermissionLevel4() throws MalformedURLException, ParseException { + public void testGetRequestedEnvironment4() throws MalformedURLException, ParseException { String jnlpContents = minimalJnlp.replace("SECURITY", "unknown"); URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false, false, false)); - Assert.assertEquals(ApplicationPermissionLevel.NONE, jnlpFile.getApplicationPermissionLevel()); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false, false, false), null); + Assert.assertEquals(ApplicationEnvironment.SANDBOX, jnlpFile.getApplicationEnvironment()); } @Test - public void testGetRequestedPermissionLevel5() throws MalformedURLException, ParseException { - String jnlpContents = minimalJnlp.replace("SECURITY", "<"+ ApplicationPermissionLevel.J2EE.getValue()+"/>"); + public void testGetRequestedEnvironment5() throws MalformedURLException, ParseException { + String jnlpContents = minimalJnlp.replace("SECURITY", "<"+ ApplicationEnvironment.J2EE.getValue()+"/>"); URL codeBase = new URL("http://icedtea.classpath.org"); InputStream is = new ByteArrayInputStream(jnlpContents.getBytes()); - JNLPFile jnlpFile = new JNLPFile(is, codeBase, new ParserSettings(false, false, false)); - Assert.assertEquals(ApplicationPermissionLevel.J2EE, jnlpFile.getApplicationPermissionLevel()); + JNLPFile jnlpFile = new JNLPFile(is, null, codeBase, new ParserSettings(false, false, false), null); + Assert.assertEquals(ApplicationEnvironment.J2EE, jnlpFile.getApplicationEnvironment()); } @Test diff --git a/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTest.java b/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTest.java index a88b62c00..693bd6983 100644 --- a/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTest.java @@ -34,333 +34,335 @@ package net.sourceforge.jnlp; +import net.adoptopenjdk.icedteaweb.io.FileUtils; +import net.adoptopenjdk.icedteaweb.io.IOUtils; import net.adoptopenjdk.icedteaweb.testing.annotations.KnownToFail; -import org.junit.Assert; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; import java.util.Random; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class JNLPMatcherTest { - static final String tests[] = { - "Testing template with CDATA", - "Testing template with an exact duplicate of the launching JNLP file", - "Testing template with wildchars as attribute/element values", - "Testing template with attributes/elements in different order", - "Testing template with wildchars as ALL element/attribute values", - "Testing template with comments", - "Testing template with different attribute/element values", - "Testing template by adding an additional children to element", - "Testing template by removing children from element", - "Testing template with a complete different JNLP template file ", - "Testing application with CDATA", - "Testing application with an exact duplicate of the launching JNLP file", - "Testing application with the same element/attribute name and value pair in different orders", - "Testing application with comments", - "Testing application with wildchars as attribute/element values", - "Testing application with a different codebase attribute value", - "Testing application by adding additional children to element", - "Testing application by removing children from element", - "Testing application with a complete different JNLP application file", - "Testing by calling JNLPMatcher.match() multiple times. Checking to see if the returns value is consistent" }; + private static final boolean IS_TEMPLATE = true; + private static final boolean IS_NOT_TEMPLATE = false; + private static final boolean MALFORMED_ALLOWED = false; + private static final ParserSettings DEFAULT_SETTINGS = new ParserSettings(true, true, MALFORMED_ALLOWED); + private final ClassLoader cl = ClassLoader.getSystemClassLoader(); - private final boolean MALFORMED_ALLOWED = false; - private InputStream getLaunchReader() { - return cl.getResourceAsStream("net/sourceforge/jnlp/launchApp.jnlp"); - } + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Test @KnownToFail @Ignore - public void testTemplateCDATA() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template0.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[0], true, test.isMatch()); - } + public void testTemplateCDATA() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template0.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with CDATA", test.isMatch()); } @Test - public void testTemplateDuplicate() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template1.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[1], true, test.isMatch()); - } + public void testTemplateDuplicate() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template1.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with an exact duplicate of the launching JNLP file", test.isMatch()); } @Test - public void testTemplateWildCharsRandom() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template2.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[2], true, test.isMatch()); - } + public void testTemplateWildCharsRandom() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template2.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with wildchars as attribute/element values", test.isMatch()); } @Test - public void testTemplateDifferentOrder() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template3.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[3], true, test.isMatch()); - } + public void testTemplateDifferentOrder() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template3.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with attributes/elements in different order", test.isMatch()); } @Test - public void testTemplateWildCharsAsAllValues() throws JNLPMatcherException, - IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template4.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[4], true, test.isMatch()); - } + public void testTemplateWildCharsAsAllValues() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template4.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with wildchars as ALL element/attribute values", test.isMatch()); } @Test - public void testTemplateComments() throws JNLPMatcherException, IOException { + public void testTemplateComments() throws Exception { //having comment inside element declaration is invalid but internal parser can handle it - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template5.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[5], true, test.isMatch()); - } + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template5.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing template with comments", test.isMatch()); } @Test - public void testTemplateDifferentValues() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template6.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[6], false, test.isMatch()); - } + public void testTemplateDifferentValues() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template6.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing template with different attribute/element values", test.isMatch()); } @Test - public void testTemplateExtraChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template7.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[7], false, test.isMatch()); - } + public void testTemplateExtraChild() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template7.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing template by adding an additional children to element", test.isMatch()); } @Test - public void testTemplateFewerChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template8.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[8], false, test.isMatch()); - } + public void testTemplateFewerChild() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template8.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing template by removing children from element", test.isMatch()); } @Test - public void testTemplateDifferentFile() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template9.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[9], false, test.isMatch()); - } + public void testTemplateDifferentFile() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template9.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing template with a complete different JNLP template file", test.isMatch()); } @Test @KnownToFail @Ignore - public void testApplicationCDATA() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application0.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[10], true, test.isMatch()); - } + public void testApplicationCDATA() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application0.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing application with CDATA", test.isMatch()); } @Test - public void testApplicationDuplicate() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application1.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[11], true, test.isMatch()); - } + public void testApplicationDuplicate() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application1.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing application with an exact duplicate of the launching JNLP file", test.isMatch()); } @Test - public void testApplicationDifferentOrder() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application2.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[12], true, test.isMatch()); - } + public void testApplicationDifferentOrder() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application2.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing application with the same element/attribute name and value pair in different orders", test.isMatch()); } @Test - public void testApplicationComments() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application3.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[13], true, test.isMatch()); - } + public void testApplicationComments() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application3.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertTrue("Testing application with comments", test.isMatch()); } @Test - public void testApplicationWildCharsRandom() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application4.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[14], false, test.isMatch()); - } + public void testApplicationWildCharsRandom() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application4.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing application with wildchars as attribute/element values", test.isMatch()); } @Test - public void testApplicationDifferentCodebaseValue() throws JNLPMatcherException, - IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application5.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[15], false, test.isMatch()); - } + public void testApplicationDifferentCodebaseValue() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application5.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing application with a different codebase attribute value", test.isMatch()); } @Test - public void testApplicationExtraChild() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application6.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[16], false, test.isMatch()); - } - } - - @Test - public void testApplicationFewerChild() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application7.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[17], false, test.isMatch()); - } + public void testApplicationExtraChild() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application6.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing application by adding additional children to element", test.isMatch()); } @Test - public void testApplicationDifferentFile() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[18], false, test.isMatch()); - } + public void testApplicationFewerChild() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application7.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing application by removing children from element", test.isMatch()); } - @SuppressWarnings("unused") @Test - public void testNullJNLPFiles() throws IOException { - - Exception expectedException = null; - InputStream fileStream; - try (InputStream launchReader = this.getLaunchReader()) { - fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp"); - try { - JNLPMatcher test = new JNLPMatcher(null, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with null signed application/template reader", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - try { - JNLPMatcher test = new JNLPMatcher(fileStream, null, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with null launching JNLP file reader", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - try { - JNLPMatcher test = new JNLPMatcher(null, null, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with both readers being null", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - } fileStream.close(); + public void testApplicationDifferentFile() throws Exception { + final JNLPMatcher test = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application8.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing application with a complete different JNLP application file", test.isMatch()); } @Test - public void testCallingMatchMultiple() throws JNLPMatcherException, IOException { - - // Check with application - InputStream launchReader = this.getLaunchReader(); - - InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp"); - - - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - - Assert.assertEquals(tests[19], false, test.isMatch()); - Assert.assertEquals(tests[19], false, test.isMatch()); - - fileStream.close(); - launchReader.close(); - - // Check with template - launchReader = this.getLaunchReader(); - - fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template6.jnlp"); - - test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - - Assert.assertEquals(tests[19], false, test.isMatch()); - Assert.assertEquals(tests[19], false, test.isMatch()); - - fileStream.close(); - launchReader.close(); + public void testCallingMatchMultiple() throws Exception { + final JNLPMatcher appMatcher = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/application/application8.jnlp", IS_NOT_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing by calling JNLPMatcher.match() multiple times. Checking to see if the returns value is consistent", appMatcher.isMatch()); + assertFalse("Testing by calling JNLPMatcher.match() multiple times. Checking to see if the returns value is consistent", appMatcher.isMatch()); + + final JNLPMatcher tempMatcher = new JNLPMatcher( + jarFile("net/sourceforge/jnlp/templates/template6.jnlp", IS_TEMPLATE), + jnlpFile(), + DEFAULT_SETTINGS + ); + + assertFalse("Testing by calling JNLPMatcher.match() multiple times. Checking to see if the returns value is consistent", tempMatcher.isMatch()); + assertFalse("Testing by calling JNLPMatcher.match() multiple times. Checking to see if the returns value is consistent", tempMatcher.isMatch()); } - @Test (timeout=5000 /*ms*/) - public void testIsMatchDoesNotHangOnLargeData() throws JNLPMatcherException { + @Test(timeout = 5000 /*ms*/) + public void testIsMatchDoesNotHangOnLargeData() throws Exception { /* construct an alphabet containing characters 'a' to 'z' */ - final int ALPHABET_SIZE = 26; - char[] alphabet = new char[ALPHABET_SIZE]; - for (int i = 0; i < ALPHABET_SIZE; i++) { - alphabet[i] = (char)('a' + i); - } + final char[] alphabet = "abcdefghijklmnopqrstuvwxyz".toCharArray(); + final int ALPHABET_SIZE = alphabet.length; + /* generate a long but random string using the alphabet */ final Random r = new Random(); final int STRING_SIZE = 1024 * 1024; // 1 MB - StringBuilder descriptionBuilder = new StringBuilder(STRING_SIZE); + final StringBuilder descriptionBuilder = new StringBuilder(STRING_SIZE); for (int i = 0; i < STRING_SIZE; i++) { descriptionBuilder.append(alphabet[r.nextInt(ALPHABET_SIZE)]); } - String longDescription = descriptionBuilder.toString(); + final String longDescription = descriptionBuilder.toString(); - String file = + final String file = "\n" + - " \n" + - " JNLPMatcher hangs on large file size\n" + - " IcedTea\n" + - " " + longDescription + "\n" + - " \n" + - "\n"; + " \n" + + " JNLPMatcher hangs on large file size\n" + + " IcedTea\n" + + " " + longDescription + "\n" + + " \n" + + "\n"; InputStream reader1 = new ByteArrayInputStream(file.getBytes(UTF_8)); - InputStream reader2 = new ByteArrayInputStream(file.getBytes(UTF_8)); - JNLPMatcher matcher = new JNLPMatcher(reader1, reader2, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertTrue(matcher.isMatch()); + JNLPMatcher matcher = new JNLPMatcher(jarFile(reader1, IS_NOT_TEMPLATE), jnlpFile(file), DEFAULT_SETTINGS); + assertTrue(matcher.isMatch()); + } + + private File jnlpFile() throws URISyntaxException { + final URL url = cl.getResource("net/sourceforge/jnlp/launchApp.jnlp"); + assertNotNull(url); + return Paths.get(url.toURI()).toFile(); + } + + private File jnlpFile(String content) throws IOException { + final File jnlp = File.createTempFile("jnlpMatcherTest", ".jnlp", temporaryFolder.getRoot()); + FileUtils.saveFileUtf8(content, jnlp); + return jnlp; + } + + private File jarFile(String jnlpPath, boolean isTemplate) throws IOException { + try (final InputStream inStream = cl.getResourceAsStream(jnlpPath)) { + return jarFile(inStream, isTemplate); + } + } + + private File jarFile(InputStream in, boolean isTemplate) throws IOException { + final File jar = File.createTempFile("jnlpMatcherTest", ".jar", temporaryFolder.getRoot()); + + try (final JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(jar))) { + final JarEntry jarEntry = new JarEntry(isTemplate ? JNLPMatcher.TEMPLATE : JNLPMatcher.APPLICATION); + jarOutputStream.putNextEntry(jarEntry); + IOUtils.copy(in, jarOutputStream); + } + + return jar; } } diff --git a/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTestMalformedAllowedTest.java b/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTestMalformedAllowedTest.java deleted file mode 100644 index 974b9540e..000000000 --- a/core/src/test/java/net/sourceforge/jnlp/JNLPMatcherTestMalformedAllowedTest.java +++ /dev/null @@ -1,343 +0,0 @@ -/* JNLPMatcherTest.java - Copyright (C) 2011 Red Hat, Inc. - -This file is part of IcedTea. - -IcedTea is free software; you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, version 2. - -IcedTea 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 for more details. - -You should have received a copy of the GNU General Public License along with -IcedTea; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is making a -combined work based on this library. Thus, the terms and conditions of the GNU -General Public License cover the whole combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent modules, and -to copy and distribute the resulting executable under terms of your choice, -provided that you also meet, for each linked independent module, the terms and -conditions of the license of that module. An independent module is a module -which is not derived from or based on this library. If you modify this library, -you may extend this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this exception -statement from your version. -*/ - -package net.sourceforge.jnlp; - -import net.adoptopenjdk.icedteaweb.testing.annotations.KnownToFail; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -import static java.nio.charset.StandardCharsets.UTF_8; - -public class JNLPMatcherTestMalformedAllowedTest { - - final String tests[] = JNLPMatcherTest.tests; - - private final ClassLoader cl = ClassLoader.getSystemClassLoader(); - private final boolean MALFORMED_ALLOWED = true; - - private InputStream getLaunchReader() { - return cl.getResourceAsStream("net/sourceforge/jnlp/launchApp.jnlp"); - } - - @Test - @Ignore - @KnownToFail - public void testTemplateCDATA() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template0.jnlp")) { - - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[0], true, test.isMatch()); - } - } - - @Test - public void testTemplateDuplicate() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template1.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[1], true, test.isMatch()); - } - } - - @Test - public void testTemplateWildCharsRandom() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template2.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[2], true, test.isMatch()); - } - } - - @Test - public void testTemplateDifferentOrder() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template3.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[3], true, test.isMatch()); - } - } - - @Test - public void testTemplateWildCharsAsAllValues() throws JNLPMatcherException, - IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template4.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[4], true, test.isMatch()); - } - } - - @Test - @Ignore - @KnownToFail - public void testTemplateComments() throws JNLPMatcherException, IOException { - //having comment inside element declaration is invalid anyway, so tagsoup can be excused for failing in this case - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template5.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[5], true, test.isMatch()); - } - } - - @Test - public void testTemplateDifferentValues() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template6.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[6], false, test.isMatch()); - } - } - - @Test - public void testTemplateExtraChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template7.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[7], false, test.isMatch()); - } - } - - @Test - public void testTemplateFewerChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template8.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[8], false, test.isMatch()); - } - } - - @Test - public void testTemplateDifferentFile() throws JNLPMatcherException, IOException { - - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template9.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[9], false, test.isMatch()); - } - } - - @Test - @Ignore - @KnownToFail - public void testApplicationCDATA() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application0.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[10], true, test.isMatch()); - } - } - - @Test - public void testApplicationDuplicate() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application1.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[11], true, test.isMatch()); - } - } - - @Test - public void testApplicationDifferentOrder() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application2.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[12], true, test.isMatch()); - } - } - - @Test - public void testApplicationComments() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application3.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[13], true, test.isMatch()); - } - } - - @Test - public void testApplicationWildCharsRandom() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application4.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[14], false, test.isMatch()); - } - } - - @Test - public void testApplicationDifferentCodebaseValue() throws JNLPMatcherException, - IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application5.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[15], false, test.isMatch()); - } - } - - @Test - public void testApplicationExtraChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application6.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[16], false, test.isMatch()); - } - } - - @Test - public void testApplicationFewerChild() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application7.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[17], false, test.isMatch()); - } - } - - @Test - public void testApplicationDifferentFile() throws JNLPMatcherException, IOException { - try (InputStream launchReader = this.getLaunchReader(); InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp")) { - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertEquals(tests[18], false, test.isMatch()); - } - } - - @SuppressWarnings("unused") - @Test - public void testNullJNLPFiles() throws IOException { - - Exception expectedException = null; - InputStream fileStream; - try (InputStream launchReader = this.getLaunchReader()) { - fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp"); - try { - JNLPMatcher test = new JNLPMatcher(null, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with null signed application/template reader", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - try { - JNLPMatcher test = new JNLPMatcher(fileStream, null, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with null launching JNLP file reader", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - try { - JNLPMatcher test = new JNLPMatcher(null, null, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - } catch (Exception e) { - expectedException = e; - } - Assert.assertEquals( - "Checking exception after trying to create an instance with both readers being null", - expectedException.getClass().getName(), - "net.sourceforge.jnlp.JNLPMatcherException"); - } fileStream.close(); - } - - @Test - public void testCallingMatchMultiple() throws JNLPMatcherException, IOException { - // Check with application - InputStream launchReader = this.getLaunchReader(); - InputStream fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/application/application8.jnlp"); - - JNLPMatcher test = new JNLPMatcher(fileStream, launchReader, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - - Assert.assertEquals(tests[19], false, test.isMatch()); - Assert.assertEquals(tests[19], false, test.isMatch()); - - fileStream.close(); - launchReader.close(); - - // Check with template - launchReader = this.getLaunchReader(); - - fileStream = cl - .getResourceAsStream("net/sourceforge/jnlp/templates/template6.jnlp"); - - test = new JNLPMatcher(fileStream, launchReader, true, new ParserSettings(true, true, MALFORMED_ALLOWED)); - - Assert.assertEquals(tests[19], false, test.isMatch()); - Assert.assertEquals(tests[19], false, test.isMatch()); - - fileStream.close(); - launchReader.close(); - } - - @Test (timeout=5000 /*ms*/) - public void testIsMatchDoesNotHangOnLargeData() throws JNLPMatcherException { - /* construct an alphabet containing characters 'a' to 'z' */ - final int ALPHABET_SIZE = 26; - char[] alphabet = new char[ALPHABET_SIZE]; - for (int i = 0; i < ALPHABET_SIZE; i++) { - alphabet[i] = (char)('a' + i); - } - /* generate a long but random string using the alphabet */ - final Random r = new Random(); - final int STRING_SIZE = 1024 * 1024; // 1 MB - StringBuilder descriptionBuilder = new StringBuilder(STRING_SIZE); - for (int i = 0; i < STRING_SIZE; i++) { - descriptionBuilder.append(alphabet[r.nextInt(ALPHABET_SIZE)]); - } - String longDescription = descriptionBuilder.toString(); - - String file = - "\n" + - " \n" + - " JNLPMatcher hangs on large file size\n" + - " IcedTea\n" + - " " + longDescription + "\n" + - " \n" + - "\n"; - - InputStream reader1 = new ByteArrayInputStream(file.getBytes(UTF_8)); - InputStream reader2 = new ByteArrayInputStream(file.getBytes(UTF_8)); - JNLPMatcher matcher = new JNLPMatcher(reader1, reader2, false, new ParserSettings(true, true, MALFORMED_ALLOWED)); - Assert.assertTrue(matcher.isMatch()); - } -} diff --git a/core/src/test/java/net/sourceforge/jnlp/JnlpInformationElementTest.java b/core/src/test/java/net/sourceforge/jnlp/JnlpInformationElementTest.java index 84408ccef..b7e2f5b8b 100644 --- a/core/src/test/java/net/sourceforge/jnlp/JnlpInformationElementTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/JnlpInformationElementTest.java @@ -55,7 +55,7 @@ public class JnlpInformationElementTest extends NoStdOutErrTest{ private JNLPFile setUp(final String jnlpContent) throws ParseException, MalformedURLException { final URL codeBase = new URL("http://icedtea.classpath.org"); final InputStream is = new ByteArrayInputStream(jnlpContent.getBytes()); - return new JNLPFile(is, codeBase, new ParserSettings(false,false,false)); + return new JNLPFile(is, null, codeBase, new ParserSettings(false,false,false), null); } @Test diff --git a/core/src/test/java/net/sourceforge/jnlp/ParserBasicTest.java b/core/src/test/java/net/sourceforge/jnlp/ParserBasicTest.java index 470c06880..962f1e00c 100644 --- a/core/src/test/java/net/sourceforge/jnlp/ParserBasicTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/ParserBasicTest.java @@ -47,6 +47,7 @@ import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JREDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.PropertyDesc; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.ResourcesDesc; +import net.adoptopenjdk.icedteaweb.jnlp.element.security.ApplicationEnvironment; import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFile; import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; @@ -127,7 +128,7 @@ public void testInformationDescription() throws ParseException { @Test public void testInformationOfflineAllowed() throws ParseException { InformationDesc info = parser.getInformationDescs(root).get(0); - Assert.assertEquals(true, info.isOfflineAllowed()); + Assert.assertTrue(info.isOfflineAllowed()); } @@ -208,7 +209,7 @@ public void testInformationRelatedContent() throws ParseException { public void testSecurity() throws ParseException { SecurityDesc security = parser.getSecurity(root); Assert.assertNotNull(security); - Assert.assertEquals(SecurityDesc.ALL_PERMISSIONS, security.getSecurityType()); + Assert.assertEquals(ApplicationEnvironment.ALL, security.getApplicationEnvironment()); } @Test @@ -234,23 +235,6 @@ public void testResourcesJava() throws ParseException { Assert.assertEquals("128m", jre.getMaximumHeapSize()); } - @Test - public void testResourcesInsideJava() throws ParseException { - ClassLoader cl = ParserBasicTest.class.getClassLoader(); - if (cl == null) { - cl = ClassLoader.getSystemClassLoader(); - } - ParserSettings defaultParserSettings = new ParserSettings(); - InputStream jnlpStream = cl.getResourceAsStream("net/sourceforge/jnlp/jarsInJreDesc.jnlp"); - final XMLParser xmlParser = XmlParserFactory.getParser(defaultParserSettings.getParserType()); - XmlNode omega = xmlParser.getRootNode(jnlpStream); - Parser omegaParser = new Parser(new DummyJNLPFile(), null, omega, defaultParserSettings); - ResourcesDesc resources = omegaParser.getResources(omega, false).get(0); - JARDesc[] r = resources.getJARs(); - // we ensures that also in j2se hars ar eloaded.it is 7 withutt them. - Assert.assertTrue(r.length>30); - } - @Test public void testResourcesJar() throws ParseException { ResourcesDesc resources = parser.getResources(root, false).get(0); @@ -261,18 +245,18 @@ public void testResourcesJar() throws ParseException { JARDesc[] jars = resources.getJARs(); Assert.assertEquals(3, jars.length); - for (int i = 0; i < jars.length; i++) { - if (jars[i].isNative()) { + for (JARDesc jar : jars) { + if (jar.isNative()) { foundNative = true; - Assert.assertEquals("http://localhost/native.jar", jars[i].getLocation().toString()); - } else if (jars[i].isEager()) { + Assert.assertEquals("http://localhost/native.jar", jar.getLocation().toString()); + } else if (jar.isEager()) { foundEager = true; - Assert.assertEquals("http://localhost/eager.jar", jars[i].getLocation().toString()); - } else if (jars[i].isLazy()) { + Assert.assertEquals("http://localhost/eager.jar", jar.getLocation().toString()); + } else if (jar.isLazy()) { foundLazy = true; - Assert.assertEquals("http://localhost/lazy.jar", jars[i].getLocation().toString()); + Assert.assertEquals("http://localhost/lazy.jar", jar.getLocation().toString()); } else { - Assert.assertFalse(true); + Assert.fail(); } } diff --git a/core/src/test/java/net/sourceforge/jnlp/ParserVersionStringTest.java b/core/src/test/java/net/sourceforge/jnlp/ParserVersionStringTest.java index e8da514ac..64cf5b87e 100644 --- a/core/src/test/java/net/sourceforge/jnlp/ParserVersionStringTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/ParserVersionStringTest.java @@ -64,7 +64,7 @@ public void testSpecVersionsRequiredByJnlpFile() { @Test public void testJnlpFileVersion() { - Assert.assertEquals("2.1.1-rc1", parser.getFileVersion().toString()); + Assert.assertEquals("2.1.1-rc1", String.valueOf(parser.getFileVersion())); } @Test diff --git a/core/src/test/java/net/sourceforge/jnlp/cache/CacheUtilTest.java b/core/src/test/java/net/sourceforge/jnlp/cache/CacheUtilTest.java index 986e51b63..4ebc5af41 100644 --- a/core/src/test/java/net/sourceforge/jnlp/cache/CacheUtilTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/cache/CacheUtilTest.java @@ -33,7 +33,7 @@ */ package net.sourceforge.jnlp.cache; -import net.adoptopenjdk.icedteaweb.resources.ResourceTrackerTest; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTrackerTest; import net.adoptopenjdk.icedteaweb.testing.annotations.Bug; import net.sourceforge.jnlp.util.UrlUtils; import org.junit.Assert; @@ -58,8 +58,8 @@ public final void setUp() throws Exception { @Test public void testNormalizeUrlComparisons() throws Exception { - URL[] u = ResourceTrackerTest.getUrls(); - URL[] n = ResourceTrackerTest.getNormalizedUrls(); + URL[] u = DefaultResourceTrackerTest.getUrls(); + URL[] n = DefaultResourceTrackerTest.getNormalizedUrls(); for (int i = 0; i < u.length; i++) { Assert.assertTrue("url " + i + " must CacheUtil.urlEquals to its normalized form " + i, UrlUtils.urlEquals(u[i], n[i])); Assert.assertTrue("normalized form " + i + " must CacheUtil.urlEquals to its original " + i, UrlUtils.urlEquals(n[i], u[i])); diff --git a/core/src/test/java/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java b/core/src/test/java/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java index 5d9251e14..df0d7dc83 100644 --- a/core/src/test/java/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/cache/NativeLibraryStorageTest.java @@ -35,6 +35,7 @@ package net.sourceforge.jnlp.cache; import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.resources.DefaultResourceTracker; import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; import net.adoptopenjdk.icedteaweb.testing.util.FileTestUtils; @@ -83,7 +84,7 @@ private static List makeExtensionsToTest() { /* Creates a NativeLibraryStorage object, caching the given URLs */ private static NativeLibraryStorage nativeLibraryStorageWithCache(URL... urlsToCache) { - ResourceTracker tracker = new ResourceTracker(); + ResourceTracker tracker = new DefaultResourceTracker(); for (URL urlToCache : urlsToCache) { tracker.addResource(urlToCache, VersionString.fromString("1.0"), UpdatePolicy.ALWAYS); } diff --git a/core/src/test/java/net/sourceforge/jnlp/runtime/DummySecurityDelegate.java b/core/src/test/java/net/sourceforge/jnlp/runtime/DummySecurityDelegate.java index 10aaab847..edd390157 100644 --- a/core/src/test/java/net/sourceforge/jnlp/runtime/DummySecurityDelegate.java +++ b/core/src/test/java/net/sourceforge/jnlp/runtime/DummySecurityDelegate.java @@ -1,31 +1,11 @@ package net.sourceforge.jnlp.runtime; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; -import java.net.URL; import java.security.Permission; import java.util.Collection; public class DummySecurityDelegate implements SecurityDelegate { - - @Override - public SecurityDesc getCodebaseSecurityDesc(JARDesc jarDesc, URL codebaseHost) { - return null; - } - - @Override - public SecurityDesc getClassLoaderSecurity(URL codebaseHost) throws LaunchException { - return null; - } - - @Override - public SecurityDesc getJarPermissions(URL codebaseHost) { - return null; - } - @Override public void promptUserOnPartialSigning() throws LaunchException { } diff --git a/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPFileTest.java b/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPFileTest.java index 4da9edd20..873017a8d 100644 --- a/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPFileTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPFileTest.java @@ -36,29 +36,16 @@ import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletStartupSecuritySettings; import net.adoptopenjdk.icedteaweb.jnlp.element.information.InformationDesc; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; -import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributes; import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; -import net.adoptopenjdk.icedteaweb.manifest.ManifestBoolean; -import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFileWithJar; -import net.adoptopenjdk.icedteaweb.testing.util.FileTestUtils; import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.runtime.classloader.JNLPClassLoader; import net.sourceforge.jnlp.util.logging.NoStdOutErrTest; import org.junit.AfterClass; -import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import java.io.File; -import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.jar.Attributes; -import java.util.jar.Manifest; public class JNLPFileTest extends NoStdOutErrTest { @@ -79,6 +66,7 @@ public static void resetPermissions() { JNLPRuntime.getConfiguration().setProperty(ConfigurationConstants.KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK, String.valueOf(attCheckValue)); } +/* @Test public void newSecurityAttributesTestNotSet() throws Exception { @@ -86,14 +74,21 @@ public void newSecurityAttributesTestNotSet() throws Exception { //here we go with pure loading and parsing of them File tempDirectory = FileTestUtils.createTempDirectory(); tempDirectory.deleteOnExit(); + final File fooClassFile = new File(tempDirectory, "Foo.class"); File jarLocation66 = new File(tempDirectory, "test66.jar"); File jarLocation77 = new File(tempDirectory, "test77.jar"); Manifest manifest77 = new Manifest(); + Assert.assertTrue(fooClassFile.createNewFile()); FileTestUtils.createJarWithContents(jarLocation66); //no manifest - FileTestUtils.createJarWithContents(jarLocation77, manifest77); - - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(0, jarLocation66, jarLocation77); //jar 6 should be main + FileTestUtils.createJarWithContents(jarLocation77, manifest77, fooClassFile); + + final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(0, jarLocation66, jarLocation77) { + @Override + public EntryPoint getEntryPointDesc() { + return new ApplicationDesc("Foo", new String[0]); + } + }; //jar 6 should be main final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);//jnlp file got its instance in classloaders constructor //jnlpFile.getManifestsAttributes().setLoader(classLoader); //classloader set, but no att specified @@ -106,7 +101,6 @@ public void newSecurityAttributesTestNotSet() throws Exception { Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); - Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getMainClass()); Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getApplicationName()); Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase()); Assert.assertNull("classloader attached, but should be null", jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase()); @@ -114,133 +108,138 @@ public void newSecurityAttributesTestNotSet() throws Exception { Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedLibrary()); Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedOnly()); } +*/ - @Test - public void newSecurityAttributesTest() throws Exception { - //order is tested in removeTitle - //here we go with pure loading and parsing of them - File tempDirectory = FileTestUtils.createTempDirectory(); - tempDirectory.deleteOnExit(); - File jarLocation6 = new File(tempDirectory, "test6.jar"); - File jarLocation7 = new File(tempDirectory, "test7.jar"); - Manifest manifest6 = new Manifest(); - manifest6.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass1"); //see DummyJNLPFileWithJar constructor with int - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()), "DummyClass1 title"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()), "main1 main2"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()), "*.com https://*.cz"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()), "*.net ftp://*uu.co.uk"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.CODEBASE.toString()), "*.com *.net *.cz *.co.uk"); - /* - * "sandbox" or "all-permissions" - */ - /* TODO: Commented lines with "sandbox" permissions specified are causing failures after - * PR1769 ("Permissions: sandbox" manifest attribute) patch is applied. The problem - * appears to be that the JarCertVerifier thinks that DummyJNLPFileWithJars are - * signed (jcv.isFullySigned() falls into the isTriviallySigned() case) even though - * they are completely unsigned. This *may* be only be an issue with DummyJNLPFiles. - */ - // manifest6.getMainAttributes().put(new Attributes.Name(JNLPFile.ManifestsAttributes.PERMISSIONS), "sandbox"); /* commented due to DummyJNLP being "signed" */ - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()), "all-permissions"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()), "false"); - manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()), "false"); - - Manifest manifest7 = new Manifest(); //6 must e main - manifest7.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass2"); - /* - * "sandbox" or "all-permissions" - */ - manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()), "erroneous one"); - manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()), "erroneous one"); - manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()), "erroneous one"); - - FileTestUtils.createJarWithContents(jarLocation6, manifest6); - FileTestUtils.createJarWithContents(jarLocation7, manifest7); - - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(1, jarLocation7, jarLocation6); //jar 6 should be main. Jar 7 have wrong items, but they are never loaded as in main jar are the correct one - final DummyJNLPFileWithJar errorJnlpFile = new DummyJNLPFileWithJar(0, jarLocation7); //jar 7 should be main - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationName()); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name((ManifestAttributes.CODEBASE.toString())))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); - - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationName()); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase()); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase()); - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getCodebase()); - Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedLibrary()); - Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedOnly()); - - final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); //jnlp file got its instance in classloaders constructor - //jnlpFile.getManifestsAttributes().setLoader(classLoader); +// @Test +// public void newSecurityAttributesTest() throws Exception { +// //order is tested in removeTitle +// //here we go with pure loading and parsing of them +// File tempDirectory = FileTestUtils.createTempDirectory(); +// tempDirectory.deleteOnExit(); +// File jarLocation6 = new File(tempDirectory, "test6.jar"); +// File jarLocation7 = new File(tempDirectory, "test7.jar"); +// Manifest manifest6 = new Manifest(); +// manifest6.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass1"); //see DummyJNLPFileWithJar constructor with int +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()), "DummyClass1 title"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()), "main1 main2"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()), "*.com https://*.cz"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()), "*.net ftp://*uu.co.uk"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.CODEBASE.toString()), "*.com *.net *.cz *.co.uk"); +// /* +// * "sandbox" or "all-permissions" +// */ +// /* TODO: Commented lines with "sandbox" permissions specified are causing failures after +// * PR1769 ("Permissions: sandbox" manifest attribute) patch is applied. The problem +// * appears to be that the JarCertVerifier thinks that DummyJNLPFileWithJars are +// * signed (jcv.isFullySigned() falls into the isTriviallySigned() case) even though +// * they are completely unsigned. This *may* be only be an issue with DummyJNLPFiles. +// */ +// // manifest6.getMainAttributes().put(new Attributes.Name(JNLPFile.ManifestsAttributes.PERMISSIONS), "sandbox"); /* commented due to DummyJNLP being "signed" */ +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()), "all-permissions"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()), "false"); +// manifest6.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()), "false"); +// +// Manifest manifest7 = new Manifest(); //6 must e main +// manifest7.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass2"); +// /* +// * "sandbox" or "all-permissions" +// */ +// manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()), "erroneous one"); +// manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()), "erroneous one"); +// manifest7.getMainAttributes().put(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()), "erroneous one"); +// +// FileTestUtils.createJarWithContents(jarLocation6, manifest6); +// FileTestUtils.createJarWithContents(jarLocation7, manifest7); +// +// final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(1, jarLocation7, jarLocation6); //jar 6 should be main. Jar 7 have wrong items, but they are never loaded as in main jar are the correct one +// final DummyJNLPFileWithJar errorJnlpFile = new DummyJNLPFileWithJar(0, jarLocation7); //jar 7 should be main +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationName()); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name((ManifestAttributes.CODEBASE.toString())))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.PERMISSIONS.toString()))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); +// +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationName()); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase()); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase()); +// Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getCodebase()); +// Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedLibrary()); +// Assert.assertEquals("no classloader attached, should be null", ManifestBoolean.UNDEFINED, jnlpFile.getManifestAttributesReader().isTrustedOnly()); +// +// final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); //jnlp file got its instance in classloaders constructor +// //jnlpFile.getManifestsAttributes().setLoader(classLoader); +// +// Exception ex = null; +// try { +// final JNLPClassLoader errorClassLoader = new JNLPClassLoader(errorJnlpFile, UpdatePolicy.ALWAYS);//jnlp file got its instance in classloaders constructor +// //errorJnlpFile.getManifestsAttributes().setLoader(errorClassLoader); +// } catch (Exception e) { +// //correct exception +// ex = e; +// } +// Assert.assertNotNull(ex); +// +// Assert.assertEquals("DummyClass1 title", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()))); +// Assert.assertEquals("main1 main2", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); +// Assert.assertEquals("*.com https://*.cz", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertEquals("*.net ftp://*uu.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertEquals("*.com *.net *.cz *.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); +// // Assert.assertEquals(SecurityDesc.RequestedPermissionLevel.SANDBOX.toHtmlString(), jnlpFile.getManifestsAttributes().getAttribute(new Attributes.Name(JNLPFile.ManifestsAttributes.PERMISSIONS))); /* commented due to DummyJNLP being "signed" */ +// Assert.assertEquals(AppletPermissionLevel.ALL.getValue(), jnlpFile.getManifestAttributesReader().getPermissions()); +// Assert.assertEquals("false", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); +// Assert.assertEquals("false", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); +// +// +// Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()))); +// Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); +// Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); +// Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); +// Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getPermissions()); +// Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); +// Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); +// +// Assert.assertEquals("DummyClass1 title", jnlpFile.getManifestAttributesReader().getApplicationName()); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("http://aa.com"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("https://aa.cz"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("https://aa.com"))); +// Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("http://aa.cz"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("http://aa.net"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("ftp://aa.uu.co.uk"))); +// Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("http://aa.uu.co.uk"))); +// Assert.assertEquals("*.com *.net *.cz *.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("http://aa.com"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("ftp://aa.bb.net"))); +// Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("https://x.net"))); +// Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("http://aa.bb/com"))); +// // Assert.assertEquals(JNLPFile.ManifestBoolean.TRUE, jnlpFile.getManifestsAttributes().isSandboxForced()); /* commented due to DummyJNLP being "signed" */ +// Assert.assertEquals(ManifestBoolean.FALSE, jnlpFile.getManifestAttributesReader().isTrustedLibrary()); +// Assert.assertEquals(ManifestBoolean.FALSE, jnlpFile.getManifestAttributesReader().isTrustedOnly()); +// +// ex = null; +// try { +// Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().isTrustedLibrary()); +// } catch (Exception e) { +// ex = e; +// } +// Assert.assertNotNull(ex); +// ex = null; +// try { +// Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().isTrustedOnly()); +// } catch (Exception e) { +// ex = e; +// } +// Assert.assertNotNull(ex); +// +// +// } - Exception ex = null; - try { - final JNLPClassLoader errorClassLoader = new JNLPClassLoader(errorJnlpFile, UpdatePolicy.ALWAYS);//jnlp file got its instance in classloaders constructor - //errorJnlpFile.getManifestsAttributes().setLoader(errorClassLoader); - } catch (Exception e){ - //correct exception - ex = e; - } - Assert.assertNotNull(ex); - - Assert.assertEquals("DummyClass1 title", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()))); - Assert.assertEquals("main1 main2", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); - Assert.assertEquals("*.com https://*.cz", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); - Assert.assertEquals("*.net ftp://*uu.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); - Assert.assertEquals("*.com *.net *.cz *.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); - // Assert.assertEquals(SecurityDesc.RequestedPermissionLevel.SANDBOX.toHtmlString(), jnlpFile.getManifestsAttributes().getAttribute(new Attributes.Name(JNLPFile.ManifestsAttributes.PERMISSIONS))); /* commented due to DummyJNLP being "signed" */ - Assert.assertEquals(AppletPermissionLevel.ALL.getValue(), jnlpFile.getManifestAttributesReader().getPermissions()); - Assert.assertEquals("false", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); - Assert.assertEquals("false", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); - - - Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_NAME.toString()))); - Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.ENTRY_POINT.toString()))); - Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.APPLICATION_LIBRARY_ALLOWABLE_CODEBASE.toString()))); - Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CALLER_ALLOWABLE_CODEBASE.toString()))); - Assert.assertNull(errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); - Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getPermissions()); - Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_LIBRARY.toString()))); - Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.TRUSTED_ONLY.toString()))); - - Assert.assertEquals("DummyClass1 title", jnlpFile.getManifestAttributesReader().getApplicationName()); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("http://aa.com"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("https://aa.cz"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("https://aa.com"))); - Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getApplicationLibraryAllowableCodebase().matches(new URL("http://aa.cz"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("http://aa.net"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("ftp://aa.uu.co.uk"))); - Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getCallerAllowableCodebase().matches(new URL("http://aa.uu.co.uk"))); - Assert.assertEquals("*.com *.net *.cz *.co.uk", jnlpFile.getManifestAttributesReader().getAttribute(new Attributes.Name(ManifestAttributes.CODEBASE.toString()))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("http://aa.com"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("ftp://aa.bb.net"))); - Assert.assertEquals(true, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("https://x.net"))); - Assert.assertEquals(false, jnlpFile.getManifestAttributesReader().getCodebase().matches(new URL("http://aa.bb/com"))); - // Assert.assertEquals(JNLPFile.ManifestBoolean.TRUE, jnlpFile.getManifestsAttributes().isSandboxForced()); /* commented due to DummyJNLP being "signed" */ - Assert.assertEquals(ManifestBoolean.FALSE, jnlpFile.getManifestAttributesReader().isTrustedLibrary()); - Assert.assertEquals(ManifestBoolean.FALSE, jnlpFile.getManifestAttributesReader().isTrustedOnly()); - - ex = null; - try { - Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().isTrustedLibrary()); - } catch (Exception e) { - ex = e; - } - Assert.assertNotNull(ex); - ex = null; - try { - Assert.assertEquals("erroneous one", errorJnlpFile.getManifestAttributesReader().isTrustedOnly()); - } catch (Exception e) { - ex = e; - } - Assert.assertNotNull(ex); - } +/* @Test @Ignore @@ -253,7 +252,9 @@ public void removeTitle() throws Exception { File jarLocation4 = new File(tempDirectory, "test4.jar"); File jarLocation5 = new File(tempDirectory, "test5.jar"); - /* Test with various attributes in manifest!s! */ + */ + /* Test with various attributes in manifest!s! *//* + Manifest manifest1 = new Manifest(); manifest1.getMainAttributes().put(Attributes.Name.MAIN_CLASS, "DummyClass1"); //two times, but one in main jar, see DummyJNLPFileWithJar constructor with int @@ -281,7 +282,6 @@ public void removeTitle() throws Exception { FileTestUtils.createJarWithContents(jarLocation5, manifest5); final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(3, jarLocation5, jarLocation3, jarLocation4, jarLocation1, jarLocation2); //jar 1 should be main - Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getMainClass()); Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR)); Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.IMPLEMENTATION_TITLE)); Assert.assertNull("no classloader attached, should be null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.MAIN_CLASS)); @@ -307,7 +307,6 @@ public void removeTitle() throws Exception { final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS);//jnlp file got its instance in classloaders constructor //jnlpFile.getManifestsAttributes().setLoader(classLoader); - Assert.assertNotNull("classloader attached, should be not null", jnlpFile.getManifestAttributesReader().getMainClass()); Assert.assertNull("defined twice, should be null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR)); Assert.assertNotNull("classloader attached, should be not null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.IMPLEMENTATION_TITLE)); Assert.assertNotNull("classloader attached, should be not null", jnlpFile.getManifestAttributesReader().getAttribute(Attributes.Name.MAIN_CLASS)); @@ -331,6 +330,7 @@ public void removeTitle() throws Exception { Assert.assertEquals("jnlp title (Manifested Name)", jnlpFile.getTitle()); } +*/ private void setTitle(final DummyJNLPFileWithJar jnlpFile) { setTitle(jnlpFile, "jnlp title"); @@ -338,13 +338,13 @@ private void setTitle(final DummyJNLPFileWithJar jnlpFile) { private void setTitle(final DummyJNLPFileWithJar jnlpFile, final String title) { jnlpFile.setInfo(Arrays.asList(new InformationDesc[]{ - new InformationDesc(new Locale[]{}, false) { - @Override - public String getTitle() { - return title; - } + new InformationDesc(new Locale[]{}, false) { + @Override + public String getTitle() { + return title; } - })); + } + })); } private void removeTitle(final DummyJNLPFileWithJar jnlpFile) { diff --git a/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPPolicyTest.java b/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPPolicyTest.java index 92facd398..b0324139d 100644 --- a/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPPolicyTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/runtime/JNLPPolicyTest.java @@ -12,7 +12,7 @@ public void config_location_for_windows_loads() { final String fileURI = "file://C:/Users/philippe doussot/.config/icedtea-web/security/java.policy"; System.setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); JNLPRuntime.getConfiguration().setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); - new JNLPPolicy(); + new JNLPPolicy(new JNLPSecurityManager()); } @Test @@ -20,7 +20,7 @@ public void config_location_for_nix_loads() { final String fileURI = "file://a/b/c/java.policy"; System.setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); JNLPRuntime.getConfiguration().setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); - new JNLPPolicy(); + new JNLPPolicy(new JNLPSecurityManager()); } @Test @@ -28,7 +28,7 @@ public void config_location_for_uri_loads() { final String fileURI = "http://my:8080/policy/locationjava.policy"; System.setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); JNLPRuntime.getConfiguration().setProperty(KEY_SYSTEM_SECURITY_POLICY, fileURI); - new JNLPPolicy(); + new JNLPPolicy(new JNLPSecurityManager()); } } diff --git a/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoaderTest.java b/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoaderTest.java index f246f37eb..943383a3e 100644 --- a/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoaderTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/CodeBaseClassLoaderTest.java @@ -33,38 +33,17 @@ */ package net.sourceforge.jnlp.runtime.classloader; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletStartupSecuritySettings; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.AppletPermissionLevel; -import net.adoptopenjdk.icedteaweb.jnlp.element.security.SecurityDesc; -import net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesChecker; -import net.adoptopenjdk.icedteaweb.testing.ServerAccess; -import net.adoptopenjdk.icedteaweb.testing.annotations.Bug; -import net.adoptopenjdk.icedteaweb.testing.annotations.Remote; -import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFile; import net.jcip.annotations.NotThreadSafe; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.NullJnlpFileException; -import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.util.logging.NoStdOutErrTest; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Ignore; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.net.URL; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; @NotThreadSafe @Ignore public class CodeBaseClassLoaderTest extends NoStdOutErrTest { - private static AppletSecurityLevel level; + //TODO: How to ahndle old Classloader tests? + + /* private static AppletSecurityLevel level; private static String macStatus; @BeforeClass @@ -82,26 +61,19 @@ public static void resetPermissions() { JNLPRuntime.getConfiguration().setProperty(ConfigurationConstants.KEY_ENABLE_MANIFEST_ATTRIBUTES_CHECK, macStatus); } - private static final String isWSA = "isWebstartApplication"; - static void setStaticField(Field field, Object newValue) throws Exception { field.setAccessible(true); field.set(null, newValue); } private void setWSA() throws Exception { - setStaticField(JNLPRuntime.class.getDeclaredField(isWSA), true); } private void setApplet() throws Exception { - setStaticField(JNLPRuntime.class.getDeclaredField(isWSA), false); } @AfterClass public static void tearDown() throws Exception { - setStaticField(JNLPRuntime.class.getDeclaredField(isWSA), false); - - } @Bug(id = {"PR895", @@ -231,7 +203,7 @@ protected Class findClass(String name) throws ClassNotFoundException { try { classLoader.findClass("foo"); assertFalse("should not happen", true); - } catch (ClassNotFoundException cnfe) { /* ignore */ } + } catch (ClassNotFoundException cnfe) { *//* ignore *//* } assertTrue(parentWasInvoked[0]); } @@ -267,10 +239,10 @@ public void testNullFileSecurityDesc() throws Exception { JNLPFile dummyJnlpFile = new DummyJNLPFile() { @Override public SecurityDesc getSecurity() { - return new SecurityDesc(null, AppletPermissionLevel.NONE, SecurityDesc.SANDBOX_PERMISSIONS, null); + return new SecurityDesc(null, ApplicationEnvironment.SANDBOX, null); } }; JNLPClassLoader parent = new JNLPClassLoader(dummyJnlpFile, null); - } + }*/ } diff --git a/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoaderTest.java b/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoaderTest.java index 8193cc082..0136c2bbb 100644 --- a/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoaderTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoaderTest.java @@ -32,67 +32,16 @@ */ package net.sourceforge.jnlp.runtime.classloader; -import net.adoptopenjdk.icedteaweb.StreamUtils; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletSecurityLevel; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.appletextendedsecurity.AppletStartupSecuritySettings; -import net.adoptopenjdk.icedteaweb.io.IOUtils; -import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; -import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; -import net.adoptopenjdk.icedteaweb.resources.cache.Cache; -import net.adoptopenjdk.icedteaweb.testing.ServerAccess; -import net.adoptopenjdk.icedteaweb.testing.ServerLauncher; -import net.adoptopenjdk.icedteaweb.testing.annotations.Bug; -import net.adoptopenjdk.icedteaweb.testing.mock.DummyJNLPFileWithJar; -import net.adoptopenjdk.icedteaweb.testing.util.FileTestUtils; import net.jcip.annotations.NotThreadSafe; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.JNLPFileFactory; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.config.ConfigurationConstants; -import net.sourceforge.jnlp.config.PathsAndFiles; -import net.sourceforge.jnlp.runtime.CachedJarFileCallback; -import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.util.logging.NoStdOutErrTest; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import sun.net.www.protocol.jar.URLJarFile; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.nio.file.Files; -import java.util.List; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; -import static java.util.jar.Attributes.Name.IMPLEMENTATION_TITLE; -import static java.util.jar.Attributes.Name.IMPLEMENTATION_VENDOR; -import static java.util.jar.Attributes.Name.MAIN_CLASS; -import static net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader.getAttributeFromJar; -import static net.adoptopenjdk.icedteaweb.manifest.ManifestAttributesReader.getAttributeFromJars; -import static net.adoptopenjdk.icedteaweb.testing.util.FileTestUtils.assertNoFileLeak; -import static net.sourceforge.jnlp.runtime.JNLPRuntime.getConfiguration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; @NotThreadSafe @Ignore public class JNLPClassLoaderTest extends NoStdOutErrTest { + //TODO: How to handle old Classloader tests??????? +/* private final JNLPFileFactory jnlpFileFactory = new JNLPFileFactory(); @Rule @@ -123,7 +72,7 @@ public static void restoreDialogs() { getConfiguration().setProperty(ConfigurationConstants.KEY_SECURITY_PROMPT_USER, askUser); } - /* Note: Only does file leak testing for now. */ + *//* Note: Only does file leak testing for now. *//* @Test @Ignore public void constructorFileLeakTest() throws Exception { @@ -139,23 +88,12 @@ public void constructorFileLeakTest() throws Exception { }); } - /* Note: We should create a JNLPClassLoader with an invalid jar to test isInvalidJar with. - * However, it is tricky without it erroring-out. */ - @Test - public void isInvalidJarTest() throws Exception { - final File jarLocation = createJarWithoutContent(); - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation); - final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); - - assertNoFileLeak(() -> assertFalse(classLoader.isInvalidJar(jnlpFile.getJarDesc()))); - } - @Test public void getMainClassNameTest() throws Exception { File tempDirectory = temporaryFolder.newFolder(); File jarLocation = new File(tempDirectory, "test.jar"); - /* Test with main-class in manifest */ + *//* Test with main-class in manifest *//* Manifest manifest = new Manifest(); manifest.getMainAttributes().put(MAIN_CLASS, "DummyClass"); FileTestUtils.createJarWithContents(jarLocation, manifest); @@ -169,7 +107,7 @@ public void getMainClassNameTest() throws Exception { @Test @Ignore public void getMainClassNameTestEmpty() throws Exception { - /* Test with-out any main-class specified */ + *//* Test with-out any main-class specified *//* File jarLocation = createJarWithoutContent(); final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation); @@ -178,29 +116,12 @@ public void getMainClassNameTestEmpty() throws Exception { assertNoFileLeak(() -> assertNull(jnlpFile.getManifestAttributesReader().getMainClass(jnlpFile.getJarLocation(), classLoader.getTracker()))); } - /* Note: Although it does a basic check, this mainly checks for file-descriptor leak */ - @Test - public void checkForMainFileLeakTest() throws Exception { - File jarLocation = createJarWithoutContent(); - - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation); - final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); - assertNoFileLeak(() -> { - try { - classLoader.checkForMain(asList(jnlpFile.getJarDesc())); - } catch (LaunchException e) { - fail(e.toString()); - } - }); - assertFalse(classLoader.hasMainJar()); - } - @Test public void getCustomAttributes() throws Exception { File tempDirectory = temporaryFolder.newFolder(); File jarLocation = new File(tempDirectory, "testX.jar"); - /* Test with attributes in manifest */ + *//* Test with attributes in manifest *//* Manifest manifest = new Manifest(); manifest.getMainAttributes().put(MAIN_CLASS, "DummyClass"); manifest.getMainAttributes().put(IMPLEMENTATION_TITLE, "it"); @@ -217,20 +138,6 @@ public void getCustomAttributes() throws Exception { }); } - @Test - public void getCustomAttributesEmpty() throws Exception { - File jarLocation = createJarWithoutContent(); - - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation); - final JNLPClassLoader classLoader = new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS); - - assertNoFileLeak(() -> { - assertNull(getAttributeFromJar(IMPLEMENTATION_VENDOR, jnlpFile.getJarLocation(), classLoader.getTracker())); - assertNull(getAttributeFromJar(MAIN_CLASS, jnlpFile.getJarLocation(), classLoader.getTracker())); - assertNull(getAttributeFromJar(IMPLEMENTATION_TITLE, jnlpFile.getJarLocation(), classLoader.getTracker())); - }); - } - @Test public void checkOrderWhenReadingAttributes() throws Exception { File tempDirectory = temporaryFolder.newFolder(); @@ -240,7 +147,7 @@ public void checkOrderWhenReadingAttributes() throws Exception { File jarLocation4 = new File(tempDirectory, "test4.jar"); File jarLocation5 = new File(tempDirectory, "test5.jar"); - /* Test with various attributes in manifest!s! */ + *//* Test with various attributes in manifest!s! *//* Manifest manifest1 = new Manifest(); manifest1.getMainAttributes().put(MAIN_CLASS, "DummyClass1"); //two times, but one in main jar, see DummyJNLPFileWithJar constructor with int @@ -288,7 +195,7 @@ public void tryNullManifest() throws Exception { File jarLocation = new File(tempDirectory, "test-npe.jar"); File dummyContent = File.createTempFile("dummy", "context", tempDirectory); - /* Test with-out any attribute specified specified */ + *//* Test with-out any attribute specified specified *//* FileTestUtils.createJarWithoutManifestContents(jarLocation, dummyContent); final Exception[] exs = new Exception[2]; @@ -311,41 +218,6 @@ public void tryNullManifest() throws Exception { Assert.assertNull(exs[1]); } - @Test - @Bug(id = "PR3417") - /** - * The nested jar must be more 1024 bytes long. Better, longer - * then byte[] bytes = new byte[1024] on line 1273 in - * net.sourceforge.jnlp.runtime.JNLPClassLoader otherwise the file - * will not get rewritten while read Also there must be more then - * one item of this size, for same reason - */ - public void testNameClashInNestedJars() throws Exception { - //for this test is enough to not crash jvm - final boolean verifyBackup = JNLPRuntime.isVerifying(); - final File dir = temporaryFolder.newFolder(); - final File dirHolder = File.createTempFile("pf-", ".jar", dir); - final File jarLocation = new File(dirHolder.getParentFile(), "pf.jar"); - try { - //it is invalid jar, so we have to disable checks first - JNLPRuntime.setVerify(false); - InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("net/sourceforge/jnlp/runtime/pf.jar-orig"); - assertNotNull(is); - Files.copy(is, jarLocation.toPath()); - final DummyJNLPFileWithJar jnlpFile = new DummyJNLPFileWithJar(jarLocation); - - new JNLPClassLoader(jnlpFile, UpdatePolicy.ALWAYS) { - @Override - protected void activateJars(List jars) { - super.activateJars(jars); - } - - }; - } finally { - JNLPRuntime.setVerify(verifyBackup); - } - } - @Test public void testFindLibrary() throws Exception { final File tempDirectory = temporaryFolder.newFolder(); @@ -409,7 +281,7 @@ public void testRelativePathInUrl() throws Exception { try { final URL jnlpUrl = new URL("http://localhost:" + port + "/up.jnlp"); final JNLPFile jnlpFile1 = jnlpFileFactory.create(jnlpUrl); - final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false); + final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false, new DefaultResourceTracker(true, jnlpFile1.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy())); openResourceAsStream(classLoader1, "Hello1.class"); openResourceAsStream(classLoader1, "META-INF/MANIFEST.MF"); assertTrue(Cache.isAnyCached(jnlpUrl, null)); @@ -460,7 +332,7 @@ public void testEncodedPathIsNotDecodedForCache() throws Exception { try { final URL jnlpUrl = new URL("http://localhost:" + port + "/upEncoded.jnlp"); final JNLPFile jnlpFile1 = jnlpFileFactory.create(jnlpUrl); - final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false); + final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false, new DefaultResourceTracker(true, jnlpFile1.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy())); openResourceAsStream(classLoader1, "Hello1.class"); openResourceAsStream(classLoader1, "META-INF/MANIFEST.MF"); assertTrue(Cache.isAnyCached(jnlpUrl, null)); @@ -513,7 +385,7 @@ public void testRelativePathInNestedJars() throws Exception { //it is invalid jar, so we have to disable checks first final URL jnlpUrl = new URL("http://localhost:" + port + "/jar_03_dotdot_jarN1.jnlp"); final JNLPFile jnlpFile = jnlpFileFactory.create(jnlpUrl); - final JNLPClassLoader classLoader = JNLPClassLoader.getInstance(jnlpFile, UpdatePolicy.ALWAYS, false); + final JNLPClassLoader classLoader = JNLPClassLoader.getInstance(jnlpFile, UpdatePolicy.ALWAYS, false, new DefaultResourceTracker(true, jnlpFile.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy())); //ThreadGroup group = Thread.currentThread().getThreadGroup(); //ApplicationInstance app = new ApplicationInstance(jnlpFile, group, classLoader); @@ -596,7 +468,7 @@ public void testLoadClass() throws Exception { try { final URL jnlpUrl = new URL("http://localhost:" + port + "/test.jnlp"); final JNLPFile jnlpFile1 = jnlpFileFactory.create(jnlpUrl); - final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false); + final JNLPClassLoader classLoader1 = JNLPClassLoader.getInstance(jnlpFile1, UpdatePolicy.ALWAYS, false, new DefaultResourceTracker(true, jnlpFile1.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy())); classLoader1.loadClass("Hello1"); } finally { JNLPRuntime.setVerify(verifyBackup); @@ -655,7 +527,7 @@ public void testDifferentSignatureInManifestMf() throws Exception { try { //it is invalid jar, so we have to disable checks first final JNLPFile jnlpFile = jnlpFileFactory.create(new URL("http://localhost:" + port + "/jar_03_dotdot_jarN1.jnlp")); - JNLPClassLoader.getInstance(jnlpFile, UpdatePolicy.ALWAYS, false); + JNLPClassLoader.getInstance(jnlpFile, UpdatePolicy.ALWAYS, false, new DefaultResourceTracker(true, jnlpFile.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy())); } finally { JNLPRuntime.setVerify(verifyBackup); JNLPRuntime.setTrustAll(trustBackup); @@ -681,7 +553,7 @@ private void clearCache() { private File createJarWithoutContent() throws Exception { File tempDirectory = temporaryFolder.newFolder(); File jarLocation = new File(tempDirectory, "test.jar"); - FileTestUtils.createJarWithContents(jarLocation /* no contents*/); + FileTestUtils.createJarWithContents(jarLocation *//* no contents*//*); return jarLocation; - } + }*/ } diff --git a/core/src/test/java/net/sourceforge/jnlp/services/SingleInstanceLockTest.java b/core/src/test/java/net/sourceforge/jnlp/services/SingleInstanceLockTest.java index ff3d92f7a..b08985715 100755 --- a/core/src/test/java/net/sourceforge/jnlp/services/SingleInstanceLockTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/services/SingleInstanceLockTest.java @@ -1,6 +1,5 @@ package net.sourceforge.jnlp.services; -import net.adoptopenjdk.icedteaweb.io.FileUtils; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.ParserSettings; import net.sourceforge.jnlp.util.logging.NoStdOutErrTest; @@ -17,9 +16,10 @@ public final class SingleInstanceLockTest extends NoStdOutErrTest { @Test public void testCreateWithPort() throws Exception { + URL codeBase = new URL("http://icedtea.classpath.org"); final URL url = this.getClass().getClassLoader().getResource("net/sourceforge/jnlp/basic.jnlp"); assertNotNull(url); - final JNLPFile jnlpFile = new JNLPFile(url.openStream(), url, new ParserSettings(false, false, false)); + final JNLPFile jnlpFile = new JNLPFile(url.openStream(), url, codeBase, new ParserSettings(false, false, false), null); assertNotNull(jnlpFile); final SingleInstanceLock sil = new SingleInstanceLock(jnlpFile); diff --git a/core/src/test/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJarTest.java b/core/src/test/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJarTest.java new file mode 100644 index 000000000..36ae4000f --- /dev/null +++ b/core/src/test/java/net/sourceforge/jnlp/signing/CertificatesFullySigningTheJarTest.java @@ -0,0 +1,150 @@ +package net.sourceforge.jnlp.signing; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CertificatesFullySigningTheJarTest { + + @Test(expected = NullPointerException.class) + public void testFailOnNullCertificate() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("unsigned.jar"); + + //when + certificates.contains((Certificate)null); + } + + @Test(expected = NullPointerException.class) + public void testFailOnNullCertPath() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("unsigned.jar"); + + //when + certificates.contains((CertPath) null); + } + + @Test + public void testUnsignedJarHasNoCertificates() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("unsigned.jar"); + + //than + assertTrue(certificates.getCertificates().isEmpty()); + } + + @Test + public void testUnsignedJarIsNotSignedByCertificate() throws Exception { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("signed.jar"); + final Certificate certificate = generateTestCertificate(); + + //when + final boolean fullySigned = certificates.contains(certificate); + + //than + assertFalse(fullySigned); + } + + @Test + public void testSignedJarHasCertificates() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("signed.jar"); + + //than + assertFalse(certificates.getCertificatePaths().isEmpty()); + } + + @Test + public void testSignedJarIsNotSignedByAnotherCertificate() throws Exception { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("signed.jar"); + final Certificate certificate = generateTestCertificate(); + + //when + final boolean fullySigned = certificates.contains(certificate); + + //than + assertFalse(fullySigned); + } + + @Test + public void testSignedJarIsSignedBySignerCertificatePath() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("signed.jar"); + final CertPath certPath = certificates.getCertificatePaths().iterator().next(); + + //when + final boolean fullySigned = certificates.contains(certPath); + + //than + assertTrue(fullySigned); + } + + @Test + public void testSignedJarIsSignedByCertificate() { + + //given + final CertificatesFullySigningTheJar certificates = determineCertificatesFullySigningThe("signed.jar"); + + //when + final Set certs = certificates.getCertificatePaths().stream() + .flatMap(certPath -> certPath.getCertificates().stream()) + .collect(Collectors.toSet()); + + //than + assertFalse(certs.isEmpty()); + certs.forEach(c -> assertTrue(certificates.contains(c))); + } + + private CertificatesFullySigningTheJar determineCertificatesFullySigningThe(String fileName) { + final File jarFile = getResourceAsFile(fileName); + return SignVerifyUtils.determineCertificatesFullySigningThe(jarFile); + } + + private File getResourceAsFile(String fileName) { + return new File(CertificatesFullySigningTheJarTest.class.getResource(fileName).getFile()); + } + + private Certificate generateTestCertificate() throws Exception { + //Source: http://www.javased.com/index.php?source_dir=spring-security-oauth/spring-security-oauth/src/test/java/org/springframework/security/oauth/common/signature/TestRSA_SHA1SignatureMethod.java + final String googleOAuthCert = "-----BEGIN CERTIFICATE-----\n" + + "MIIDBDCCAm2gAwIBAgIJAK8dGINfkSTHMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV\n" + + "BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEG\n" + + "A1UEChMKR29vZ2xlIEluYzEXMBUGA1UEAxMOd3d3Lmdvb2dsZS5jb20wHhcNMDgx\n" + + "MDA4MDEwODMyWhcNMDkxMDA4MDEwODMyWjBgMQswCQYDVQQGEwJVUzELMAkGA1UE\n" + + "CBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBJ\n" + + "bmMxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + + "ADCBiQKBgQDQUV7ukIfIixbokHONGMW9+ed0E9X4m99I8upPQp3iAtqIvWs7XCbA\n" + + "bGqzQH1qX9Y00hrQ5RRQj8OI3tRiQs/KfzGWOdvLpIk5oXpdT58tg4FlYh5fbhIo\n" + + "VoVn4GvtSjKmJFsoM8NRtEJHL1aWd++dXzkQjEsNcBXwQvfDb0YnbQIDAQABo4HF\n" + + "MIHCMB0GA1UdDgQWBBSm/h1pNY91bNfW08ac9riYzs3cxzCBkgYDVR0jBIGKMIGH\n" + + "gBSm/h1pNY91bNfW08ac9riYzs3cx6FkpGIwYDELMAkGA1UEBhMCVVMxCzAJBgNV\n" + + "BAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUg\n" + + "SW5jMRcwFQYDVQQDEw53d3cuZ29vZ2xlLmNvbYIJAK8dGINfkSTHMAwGA1UdEwQF\n" + + "MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAYpHTr3vQNsHHHUm4MkYcDB20a5KvcFoX\n" + + "gCcYtmdyd8rh/FKeZm2me7eQCXgBfJqQ4dvVLJ4LgIQiU3R5ZDe0WbW7rJ3M9ADQ\n" + + "FyQoRJP8OIMYW3BoMi0Z4E730KSLRh6kfLq4rK6vw7lkH9oynaHHWZSJLDAp17cP\n" + + "j+6znWkN9/g=\n" + + "-----END CERTIFICATE-----"; + return CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(googleOAuthCert.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/core/src/test/java/net/sourceforge/jnlp/tools/JarCertVerifierTest.java b/core/src/test/java/net/sourceforge/jnlp/signing/JarCertVerifierTest.java similarity index 82% rename from core/src/test/java/net/sourceforge/jnlp/tools/JarCertVerifierTest.java rename to core/src/test/java/net/sourceforge/jnlp/signing/JarCertVerifierTest.java index 0ab30325a..23533ea7e 100644 --- a/core/src/test/java/net/sourceforge/jnlp/tools/JarCertVerifierTest.java +++ b/core/src/test/java/net/sourceforge/jnlp/signing/JarCertVerifierTest.java @@ -32,20 +32,21 @@ statement from your version. */ -package net.sourceforge.jnlp.tools; +package net.sourceforge.jnlp.signing; -import java.security.CodeSigner; -import java.util.Date; -import java.util.List; -import java.util.Vector; -import java.util.jar.JarEntry; import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; import net.adoptopenjdk.icedteaweb.testing.tools.CodeSignerCreator; -import net.sourceforge.jnlp.tools.JarCertVerifier.VerifyResult; +import net.sourceforge.jnlp.signing.JarCertVerifier.SignVerifyResult; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import java.security.CodeSigner; +import java.util.Date; +import java.util.List; +import java.util.Vector; +import java.util.jar.JarEntry; + import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -55,22 +56,22 @@ public class JarCertVerifierTest { @Test public void testIsMetaInfFile() { final String METAINF = "META-INF"; - assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/file.MF")); - assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/file.SF")); - assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/file.DSA")); - assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/file.RSA")); - assertTrue(JarCertVerifier.isMetaInfFile(METAINF + "/SIG-blah.blah")); - - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/file.MF.class")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/file.SF.class")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/file.DSA.class")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/file.RSA.class")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/SIG-blah.blah.class")); - - assertFalse(JarCertVerifier.isMetaInfFile("some_dir/" + METAINF + "/filename")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "filename")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/filename")); - assertFalse(JarCertVerifier.isMetaInfFile(METAINF + "/filename")); + assertTrue(SignVerifyUtils.isMetaInfFile(METAINF + "/file.MF")); + assertTrue(SignVerifyUtils.isMetaInfFile(METAINF + "/file.SF")); + assertTrue(SignVerifyUtils.isMetaInfFile(METAINF + "/file.DSA")); + assertTrue(SignVerifyUtils.isMetaInfFile(METAINF + "/file.RSA")); + assertTrue(SignVerifyUtils.isMetaInfFile(METAINF + "/SIG-blah.blah")); + + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/file.MF.class")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/file.SF.class")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/file.DSA.class")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/file.RSA.class")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/SIG-blah.blah.class")); + + assertFalse(SignVerifyUtils.isMetaInfFile("some_dir/" + METAINF + "/filename")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "filename")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/filename")); + assertFalse(SignVerifyUtils.isMetaInfFile(METAINF + "/filename")); } class JarCertVerifierEntry extends JarEntry { @@ -113,67 +114,67 @@ public static void setUp() throws Exception { @Test public void testNoManifest() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); - VerifyResult result = jcv.verifyJarEntryCerts("", false, null); + JarCertVerifier jcv = new JarCertVerifier(); + SignVerifyResult result = jcv.verifyJarEntryCerts("", false, null); Assert.assertEquals("No manifest should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("No manifest means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testNoSignableEntries() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("OneDirEntry/")); entries.add(new JarCertVerifierEntry("META-INF/MANIFEST.MF")); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("No signable entry (only dirs/manifests) should be considered trivially signed.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("No signable entry (only dirs/manifests) means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testSingleEntryNoSigners() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner")); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One unsigned entry should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("One unsigned entry means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testManyEntriesNoSigners() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstEntryWithoutSigner")); entries.add(new JarCertVerifierEntry("secondEntryWithoutSigner")); entries.add(new JarCertVerifierEntry("thirdEntryWithoutSigner")); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Many unsigned entries should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("Many unsigned entries means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testSingleEntrySingleValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] signers = { alphaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByOne", signers)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One signed entry should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("One signed entry means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("One signed entry means one signer in the verifier.", @@ -182,16 +183,16 @@ public void testSingleEntrySingleValidSigner() throws Exception { @Test public void testManyEntriesSingleValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] signers = { alphaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByOne", signers)); entries.add(new JarCertVerifierEntry("secondSignedByOne", signers)); entries.add(new JarCertVerifierEntry("thirdSignedByOne", signers)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by one signer should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries signed by one signer means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by one signer means one signer in the verifier.", @@ -200,14 +201,14 @@ public void testManyEntriesSingleValidSigner() throws Exception { @Test public void testSingleEntryMultipleValidSigners() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByThree", signers)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by three signers should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("One entry signed by three means three signers in the verifier.", 3, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by three means three signers in the verifier.", @@ -218,16 +219,16 @@ public void testSingleEntryMultipleValidSigners() throws Exception { @Test public void testManyEntriesMultipleValidSigners() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] signers = { alphaSigner, betaSigner, charlieSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByThree", signers)); entries.add(new JarCertVerifierEntry("secondSignedByThree", signers)); entries.add(new JarCertVerifierEntry("thirdSignedByThree", signers)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by three signers should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries signed by three means three signers in the verifier.", 3, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by three means three signers in the verifier.", @@ -238,7 +239,7 @@ public void testManyEntriesMultipleValidSigners() throws Exception { @Test public void testOneCommonSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] alphaSigners = { alphaSigner }; CodeSigner[] betaSigners = { alphaSigner, betaSigner }; CodeSigner[] charlieSigners = { alphaSigner, charlieSigner }; @@ -246,10 +247,10 @@ public void testOneCommonSigner() throws Exception { entries.add(new JarCertVerifierEntry("firstSignedByOne", alphaSigners)); entries.add(new JarCertVerifierEntry("secondSignedByTwo", betaSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByTwo", charlieSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by at least one common signer should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries signed completely by only one signer means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed completely by only one signer means one signer in the verifier.", @@ -258,7 +259,7 @@ public void testOneCommonSigner() throws Exception { @Test public void testNoCommonSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] alphaSigners = { alphaSigner }; CodeSigner[] betaSigners = { betaSigner }; CodeSigner[] charlieSigners = { charlieSigner }; @@ -266,57 +267,57 @@ public void testNoCommonSigner() throws Exception { entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); entries.add(new JarCertVerifierEntry("secondSignedByBeta", betaSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByCharlie", charlieSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by no common signers should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("Three entries signed by no common signers means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testFewButNotAllCommonSigners() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] alphaSigners = { alphaSigner }; CodeSigner[] betaSigners = { betaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByBeta", betaSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("First two entries signed by alpha signer, third entry signed by beta signer should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("Three entries signed by some common signers but not all means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testNotAllEntriesSigned() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] alphaSigners = { alphaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByAlpha", alphaSigners)); entries.add(new JarCertVerifierEntry("secondSignedByAlpha", alphaSigners)); entries.add(new JarCertVerifierEntry("thirdUnsigned")); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, should be considered unsigned.", - VerifyResult.UNSIGNED, result); + SignVerifyResult.UNSIGNED, result); Assert.assertEquals("First two entries signed by alpha signer, third entry not signed, means no signers in the verifier.", 0, jcv.getCertsList().size()); } @Test public void testSingleEntryExpiredSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiredSigners = { expiredSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by expired cert, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("One entry signed by expired cert means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by expired cert means one signer in the verifier.", @@ -325,16 +326,16 @@ public void testSingleEntryExpiredSigner() throws Exception { @Test public void testManyEntriesExpiredSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiredSigners = { expiredSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpired", expiredSigners)); entries.add(new JarCertVerifierEntry("secondSignedBExpired", expiredSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by expired cert, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("Three entries signed by expired cert means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by expired cert means one signer in the verifier.", @@ -343,14 +344,14 @@ public void testManyEntriesExpiredSigner() throws Exception { @Test public void testSingleEntryExpiringSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiringSigners = { expiringSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by expiring cert, should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("One entry signed by expiring cert means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by expiring cert means one signer in the verifier.", @@ -359,16 +360,16 @@ public void testSingleEntryExpiringSigner() throws Exception { @Test public void testManyEntriesExpiringSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiringSigners = { expiringSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpiring", expiringSigners)); entries.add(new JarCertVerifierEntry("secondSignedBExpiring", expiringSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByExpiring", expiringSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by expiring cert, should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries signed by expiring cert means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by expiring cert means one signer in the verifier.", @@ -377,14 +378,14 @@ public void testManyEntriesExpiringSigner() throws Exception { @Test public void testSingleEntryNotYetValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] notYetValidSigners = { notYetValidSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by cert that is not yet valid, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("One entry signed by cert that is not yet valid means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by cert that is not yet valid means one signer in the verifier.", @@ -393,16 +394,16 @@ public void testSingleEntryNotYetValidSigner() throws Exception { @Test public void testManyEntriesNotYetValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] notYetValidSigners = { notYetValidSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByNotYetValid", notYetValidSigners)); entries.add(new JarCertVerifierEntry("secondSignedByNotYetValid", notYetValidSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByNotYetValid", notYetValidSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by cert that is not yet valid, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("Three entries signed by cert that is not yet valid means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by cert that is not yet valid means one signer in the verifier.", @@ -411,14 +412,14 @@ public void testManyEntriesNotYetValidSigner() throws Exception { @Test public void testSingleEntryExpiringAndNotYetValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by cert that is not yet valid but also expiring means one signer in the verifier.", @@ -427,17 +428,17 @@ public void testSingleEntryExpiringAndNotYetValidSigner() throws Exception { @Test public void testManyEntryExpiringAndNotYetValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] expiringAndNotYetValidSigners = { expiringAndNotYetValidSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); entries.add(new JarCertVerifierEntry("secondSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByExpiringNotYetValid", expiringAndNotYetValidSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by cert that is not yet valid but also expiring means one signer in the verifier.", @@ -448,14 +449,14 @@ public void testManyEntryExpiringAndNotYetValidSigner() throws Exception { @Test public void testSingleEntryOneExpiredOneValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("One entry signed by one expired cert and another valid cert, should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("One entry signed by one expired cert and another valid cert means two signers in the verifier.", 2, jcv.getCertsList().size()); Assert.assertTrue("One entry signed by one expired cert and another valid cert means two signers in the verifier.", @@ -465,16 +466,16 @@ public void testSingleEntryOneExpiredOneValidSigner() throws Exception { @Test public void testManyEntriesOneExpiredOneValidSigner() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] oneExpiredOneValidSigner = { expiredSigner, alphaSigner }; Vector entries = new Vector(); entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigner)); entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigner)); entries.add(new JarCertVerifierEntry("thirdSignedByTwo", oneExpiredOneValidSigner)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries signed by one expired cert and another valid cert, should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries signed by one expired cert and another valid cert means two signers in the verifier.", 2, jcv.getCertsList().size()); Assert.assertTrue("Three entries signed by one expired cert and another valid cert means two signers in the verifier.", @@ -484,7 +485,7 @@ public void testManyEntriesOneExpiredOneValidSigner() throws Exception { @Test public void testSomeExpiredEntries() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] oneExpiredOneValidSigners = { expiredSigner, alphaSigner }; CodeSigner[] expiredSigners = { expiredSigner }; @@ -492,10 +493,10 @@ public void testSomeExpiredEntries() throws Exception { entries.add(new JarCertVerifierEntry("firstSignedByTwo", oneExpiredOneValidSigners)); entries.add(new JarCertVerifierEntry("secondSignedByTwo", oneExpiredOneValidSigners)); entries.add(new JarCertVerifierEntry("thirdSignedByExpired", expiredSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert, should be considered signed but not okay.", - VerifyResult.SIGNED_NOT_OK, result); + SignVerifyResult.SIGNED_NOT_OK, result); Assert.assertEquals("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Two entries signed by one expired and one valid cert, third signed by just expired cert means one signer in the verifier.", @@ -504,7 +505,7 @@ public void testSomeExpiredEntries() throws Exception { @Test public void testManyInvalidOneValidStillSignedOkay() throws Exception { - JarCertVerifier jcv = new JarCertVerifier(null); + JarCertVerifier jcv = new JarCertVerifier(); CodeSigner[] oneExpiredOneValidSigners = { alphaSigner, expiredSigner }; CodeSigner[] oneNotYetValidOneValidSigners = { alphaSigner, notYetValidSigner }; CodeSigner[] oneExpiringSigners = { alphaSigner, expiringSigner }; @@ -516,10 +517,10 @@ public void testManyInvalidOneValidStillSignedOkay() throws Exception { entries.add(new JarCertVerifierEntry("thirdSigned", oneExpiringSigners)); entries.add(new JarCertVerifierEntry("oneDir/")); entries.add(new JarCertVerifierEntry("oneDir/fourthSigned", oneExpiredOneValidSigners)); - VerifyResult result = jcv.verifyJarEntryCerts("", true, entries); + SignVerifyResult result = jcv.verifyJarEntryCerts("", true, entries); Assert.assertEquals("Three entries sharing valid cert and others with issues, should be considered signed and okay.", - VerifyResult.SIGNED_OK, result); + SignVerifyResult.SIGNED_OK, result); Assert.assertEquals("Three entries sharing valid cert and others with issues means one signer in the verifier.", 1, jcv.getCertsList().size()); Assert.assertTrue("Three entries sharing valid cert and others with issues means one signer in the verifier.", diff --git a/core/src/test/java/net/sourceforge/jnlp/signing/SignVerifyUtilsTest.java b/core/src/test/java/net/sourceforge/jnlp/signing/SignVerifyUtilsTest.java new file mode 100644 index 000000000..94dee8ac7 --- /dev/null +++ b/core/src/test/java/net/sourceforge/jnlp/signing/SignVerifyUtilsTest.java @@ -0,0 +1,12 @@ +package net.sourceforge.jnlp.signing; + +import org.junit.Test; + +public class SignVerifyUtilsTest { + + @Test(expected = NullPointerException.class) + public void testFailOnNullResource() { + SignVerifyUtils.determineCertificatesFullySigningThe(null); + } + +} diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-lazy.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-lazy.jnlp new file mode 100644 index 000000000..485a3d3be --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-lazy.jnlp @@ -0,0 +1,14 @@ + + + + Test + IcedTea-Web + + + + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-unnamedLazy.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-unnamedLazy.jnlp new file mode 100644 index 000000000..2a3608c14 --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/eager-and-unnamedLazy.jnlp @@ -0,0 +1,13 @@ + + + + Test + IcedTea-Web + + + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/empty.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/empty.jnlp new file mode 100644 index 000000000..fe9922222 --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/empty.jnlp @@ -0,0 +1,11 @@ + + + + Test + IcedTea-Web + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/extension-with-part.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/extension-with-part.jnlp new file mode 100644 index 000000000..96b0f816a --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/extension-with-part.jnlp @@ -0,0 +1,11 @@ + + + + Test + IcedTea-Web + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-not-recursive.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-not-recursive.jnlp new file mode 100644 index 000000000..9f0e01a97 --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-not-recursive.jnlp @@ -0,0 +1,13 @@ + + + + Test + IcedTea-Web + + + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-recursive.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-recursive.jnlp new file mode 100644 index 000000000..8abd5fdc6 --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/lazy-recursive.jnlp @@ -0,0 +1,13 @@ + + + + Test + IcedTea-Web + + + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/main-1.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/main-1.jnlp new file mode 100644 index 000000000..f87420105 --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/main-1.jnlp @@ -0,0 +1,13 @@ + + + + Test + IcedTea-Web + + + + + + + + diff --git a/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/unavailable-jar.jnlp b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/unavailable-jar.jnlp new file mode 100644 index 000000000..a927babeb --- /dev/null +++ b/core/src/test/resources/net/adoptopenjdk/icedteaweb/classloader/unavailable-jar.jnlp @@ -0,0 +1,12 @@ + + + + Test + IcedTea-Web + + + + + + + diff --git a/core/src/test/resources/net/sourceforge/jnlp/signing/signed.jar b/core/src/test/resources/net/sourceforge/jnlp/signing/signed.jar new file mode 100644 index 000000000..6f69748b1 Binary files /dev/null and b/core/src/test/resources/net/sourceforge/jnlp/signing/signed.jar differ diff --git a/core/src/test/resources/net/sourceforge/jnlp/signing/unsigned.jar b/core/src/test/resources/net/sourceforge/jnlp/signing/unsigned.jar new file mode 100644 index 000000000..080383629 Binary files /dev/null and b/core/src/test/resources/net/sourceforge/jnlp/signing/unsigned.jar differ diff --git a/integration-tests/.gitignore b/integration-tests/.gitignore new file mode 100644 index 000000000..d392f0e82 --- /dev/null +++ b/integration-tests/.gitignore @@ -0,0 +1 @@ +*.jar diff --git a/integration-tests/classloader-integration-tests-module-1/pom.xml b/integration-tests/classloader-integration-tests-module-1/pom.xml new file mode 100644 index 000000000..05aad08a1 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-1/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests-module-1 + + + ${project.artifactId} + + diff --git a/integration-tests/classloader-integration-tests-module-1/src/main/java/net/adoptopenjdk/integration/ClassA.java b/integration-tests/classloader-integration-tests-module-1/src/main/java/net/adoptopenjdk/integration/ClassA.java new file mode 100644 index 000000000..3b088793e --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-1/src/main/java/net/adoptopenjdk/integration/ClassA.java @@ -0,0 +1,4 @@ +package net.adoptopenjdk.integration; + +public class ClassA { +} diff --git a/integration-tests/classloader-integration-tests-module-2/pom.xml b/integration-tests/classloader-integration-tests-module-2/pom.xml new file mode 100644 index 000000000..bb1bf7006 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-2/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests-module-2 + + + ${project.artifactId} + + diff --git a/integration-tests/classloader-integration-tests-module-2/src/main/java/net/adoptopenjdk/integration/ClassB.java b/integration-tests/classloader-integration-tests-module-2/src/main/java/net/adoptopenjdk/integration/ClassB.java new file mode 100644 index 000000000..f1ecec4e5 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-2/src/main/java/net/adoptopenjdk/integration/ClassB.java @@ -0,0 +1,4 @@ +package net.adoptopenjdk.integration; + +public class ClassB { +} diff --git a/integration-tests/classloader-integration-tests-module-3/pom.xml b/integration-tests/classloader-integration-tests-module-3/pom.xml new file mode 100644 index 000000000..659d36486 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-3/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests-module-3 + + + ${project.artifactId} + + diff --git a/integration-tests/classloader-integration-tests-module-3/src/main/java/net/adoptopenjdk/integration/ClassC.java b/integration-tests/classloader-integration-tests-module-3/src/main/java/net/adoptopenjdk/integration/ClassC.java new file mode 100644 index 000000000..9fd3cf870 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-3/src/main/java/net/adoptopenjdk/integration/ClassC.java @@ -0,0 +1,4 @@ +package net.adoptopenjdk.integration; + +public class ClassC { +} diff --git a/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.c b/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.c new file mode 100644 index 000000000..8ad0be880 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.c @@ -0,0 +1,7 @@ +#include +#include +#include "ClassWithNativeCall.h" + +JNIEXPORT jstring JNICALL Java_net_adoptopenjdk_integration_ClassWithNativeCall_callNative(JNIEnv *env, jobject obj) { + return (*env)->NewStringUTF(env, "Hello from native world!"); +} \ No newline at end of file diff --git a/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.h b/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.h new file mode 100644 index 000000000..8092735b2 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-native/nativeLib/ClassWithNativeCall.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class net_adoptopenjdk_integration_ClassWithNativeCall */ + +#ifndef _Included_net_adoptopenjdk_integration_ClassWithNativeCall +#define _Included_net_adoptopenjdk_integration_ClassWithNativeCall +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_adoptopenjdk_integration_ClassWithNativeCall + * Method: callNative + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_net_adoptopenjdk_integration_ClassWithNativeCall_callNative + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/integration-tests/classloader-integration-tests-module-native/nativeLib/README.MD b/integration-tests/classloader-integration-tests-module-native/nativeLib/README.MD new file mode 100644 index 000000000..c02b651bf --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-native/nativeLib/README.MD @@ -0,0 +1,16 @@ +This folder contains everything to create a native lib that is called in the Java class of this module. + +# How to create the native Library + +The header file (`ClassWithNativeCall.h`) is created based on this tutorial: https://www.java-tips.org/other-api-tips-100035/148-jni/1378-simple-example-of-using-the-java-native-interface.html +The c file was written by hand. Once this is done it can be compiled. + +## Compile with GCC on MAC + +Based on the Java `System.mapLibraryName(libname)` call the native file must be called `libnativeLib.dylib` + +`gcc -I/Library/Java/JavaVirtualMachines/adoptopenjdk8u212-b03/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk8u212-b03/Contents/Home/include/darwin -dynamiclib ClassWithNativeCall.c -o libnativeLib.dylib` + +# How to add the native lib + +The Maven build of this project adds all native libraries to the JAR. By calling `new ClassWithNativeCall().callNative()` you can check if the native lib is called. \ No newline at end of file diff --git a/integration-tests/classloader-integration-tests-module-native/nativeLib/libnativeLib.dylib b/integration-tests/classloader-integration-tests-module-native/nativeLib/libnativeLib.dylib new file mode 100755 index 000000000..91b7817ad Binary files /dev/null and b/integration-tests/classloader-integration-tests-module-native/nativeLib/libnativeLib.dylib differ diff --git a/integration-tests/classloader-integration-tests-module-native/pom.xml b/integration-tests/classloader-integration-tests-module-native/pom.xml new file mode 100644 index 000000000..79ebd9091 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-native/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests-module-native + + + ${project.artifactId} + + + + maven-resources-plugin + 3.1.0 + + + copy-resources + generate-resources + + copy-resources + + + true + src/main/resources/ + + + nativeLib + + *.dylib + *.so + *.dylib + *.jnilib + *.framework + *.dll + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + src/main/resources + false + + + + + + + diff --git a/integration-tests/classloader-integration-tests-module-native/src/main/java/net/adoptopenjdk/integration/ClassWithNativeCall.java b/integration-tests/classloader-integration-tests-module-native/src/main/java/net/adoptopenjdk/integration/ClassWithNativeCall.java new file mode 100644 index 000000000..7b91fb8f1 --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-native/src/main/java/net/adoptopenjdk/integration/ClassWithNativeCall.java @@ -0,0 +1,10 @@ +package net.adoptopenjdk.integration; + +public class ClassWithNativeCall { + + static { + System.loadLibrary("nativeLib"); + } + + public native String callNative(); +} diff --git a/integration-tests/classloader-integration-tests-module-wrapper/pom.xml b/integration-tests/classloader-integration-tests-module-wrapper/pom.xml new file mode 100644 index 000000000..b9bee588e --- /dev/null +++ b/integration-tests/classloader-integration-tests-module-wrapper/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests-module-wrapper + + + + ${groupId} + classloader-integration-tests-module-1 + ${version} + provided + + + ${groupId} + classloader-integration-tests-module-2 + ${version} + provided + + + ${groupId} + classloader-integration-tests-module-3 + ${version} + provided + + + ${groupId} + classloader-integration-tests-module-native + ${version} + provided + + + diff --git a/integration-tests/classloader-integration-tests/pom.xml b/integration-tests/classloader-integration-tests/pom.xml new file mode 100644 index 000000000..1177a9c97 --- /dev/null +++ b/integration-tests/classloader-integration-tests/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + + net.adoptopenjdk + integration-tests + 3.0.0-SNAPSHOT + ../ + + + classloader-integration-tests + + + + + ${groupId} + icedtea-web-core + ${version} + test + + + org.junit.jupiter + junit-jupiter-api + 5.5.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.5.1 + test + + + + org.mockito + mockito-junit-jupiter + 2.23.0 + test + + + + ${groupId} + classloader-integration-tests-module-wrapper + ${version} + test + + + + + + + maven-resources-plugin + 3.1.0 + + + copy-resources + generate-resources + + copy-resources + + + true + src/test/resources/net/adoptopenjdk/icedteaweb/integration + + + ../classloader-integration-tests-module-1/target + + classloader-integration-tests-module-1.jar + + + + ../classloader-integration-tests-module-2/target + + classloader-integration-tests-module-2.jar + + + + ../classloader-integration-tests-module-3/target + + classloader-integration-tests-module-3.jar + + + + ../classloader-integration-tests-module-native/target + + classloader-integration-tests-module-native.jar + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/DummyResourceTracker.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/DummyResourceTracker.java new file mode 100644 index 000000000..7ae61b9b4 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/DummyResourceTracker.java @@ -0,0 +1,47 @@ +package net.adoptopenjdk.icedteaweb.integration; + +import net.adoptopenjdk.icedteaweb.jnlp.version.VersionString; +import net.adoptopenjdk.icedteaweb.resources.ResourceTracker; +import net.adoptopenjdk.icedteaweb.resources.ResourceTrackerFactory; +import net.adoptopenjdk.icedteaweb.resources.UpdatePolicy; +import net.sourceforge.jnlp.DownloadOptions; + +import java.io.File; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +public class DummyResourceTracker implements ResourceTracker { + + public static final class Factory implements ResourceTrackerFactory { + @Override + public ResourceTracker create(boolean prefetch, DownloadOptions downloadOptions, UpdatePolicy updatePolicy) { + return new DummyResourceTracker(); + } + } + + private final Set trackedUrls = new HashSet<>(); + + @Override + public void addResource(URL location, VersionString version) { + trackedUrls.add(location); + } + + @Override + public void addResource(URL location, VersionString version, UpdatePolicy updatePolicy) { + trackedUrls.add(location); + } + + @Override + public File getCacheFile(URL location) { + if (!isResourceAdded(location)) { + throw new IllegalStateException("Resource " + location + " is not known to the tracker"); + } + return new File(location.getFile()); + } + + @Override + public boolean isResourceAdded(URL location) { + return trackedUrls.contains(location); + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/IntegrationTestResources.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/IntegrationTestResources.java new file mode 100644 index 000000000..7aad683bf --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/IntegrationTestResources.java @@ -0,0 +1,10 @@ +package net.adoptopenjdk.icedteaweb.integration; + +import java.net.URL; + +public class IntegrationTestResources { + + public static URL load(final String resourceName) { + return IntegrationTestResources.class.getResource(resourceName); + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/BasicClassloaderIntegrationTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/BasicClassloaderIntegrationTests.java new file mode 100644 index 000000000..bc0fb936c --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/BasicClassloaderIntegrationTests.java @@ -0,0 +1,235 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_A; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_B; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_2; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +public class BasicClassloaderIntegrationTests { + + /** + * When loading a JNLP file the eager jars should be directly downloaded and accessible by the classloader + */ + @RepeatedTest(10) + public void testEagerJarLoadedAtStart() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-1.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * When loading a JNLP file classes from eager jar can be loaded + */ + @RepeatedTest(10) + public void testLoadClassFromEagerJar() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-1.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * When loading a JNLP file a lazy jar should not be directly downloaded + */ + @RepeatedTest(10) + public void testClassFromLazyJarNotInitialLoaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + } + + /** + * When accessing a class from a lazy jar the classloader will trigger the download of the jar and load the class + */ + @RepeatedTest(10) + public void testLoadClassFromLazyJar() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + ; + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * When accessing a class from a lazy jar the classloader will trigger the download of the jar and load the class + * Here the recursive attribute is checked + */ + @RepeatedTest(10) + public void testLoadClassFromLazyJarWithRecursive() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-7.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * if recursive attribute is not defined only direct classes in the package of a part can be downloaded. + */ + @RepeatedTest(10) + public void testLoadClassFromLazyJarWithoutRecursive() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-8.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + try { + classLoader.loadClass(CLASS_A); + Assertions.fail("should not have found the class"); + } catch (ClassNotFoundException ignored) { + } + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * When accessing a class from a lazy jar multiple times the jar is only downloaded one time + */ + @RepeatedTest(10) + public void testLazyJarOnlyDownloadedOnce() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass1 = classLoader.loadClass(CLASS_A); + final Class loadedClass2 = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass1); + Assertions.assertEquals(classLoader, loadedClass1.getClassLoader()); + Assertions.assertNotNull(loadedClass2); + Assertions.assertEquals(classLoader, loadedClass2.getClassLoader()); + Assertions.assertSame(loadedClass1, loadedClass2); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * When accessing a class from a lazy jar all jars that are in the same part will be downloaded + */ + @RepeatedTest(10) + public void testFullPartDownloaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-3.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(2, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * When a JNLP contains multiple resource tags all jars of the resources will be downloaded correctly + */ + @RepeatedTest(10) + public void testMultipleResources() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-11.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass1 = classLoader.loadClass(CLASS_A); + final Class loadedClass2 = classLoader.loadClass(CLASS_B); + + //than + Assertions.assertNotNull(loadedClass1); + Assertions.assertEquals(classLoader, loadedClass1.getClassLoader()); + Assertions.assertNotNull(loadedClass2); + Assertions.assertEquals(classLoader, loadedClass2.getClassLoader()); + Assertions.assertEquals(2, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * When a part has lazy and eager parts it will be automatically downloaded + */ + @RepeatedTest(10) + public void testEagerPart() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-21.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(2, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * If more than one lazy part matches for the needed class all parts should be downloaded + */ + @RepeatedTest(10) + public void testAllLazyPartsLoaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-23.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_B); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(2, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ClassloaderTestUtils.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ClassloaderTestUtils.java new file mode 100644 index 000000000..b28027c49 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ClassloaderTestUtils.java @@ -0,0 +1,46 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.PartExtractor; +import net.adoptopenjdk.icedteaweb.classloader.Part; +import net.adoptopenjdk.icedteaweb.integration.IntegrationTestResources; +import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; + +import java.io.IOException; +import java.util.List; + +public class ClassloaderTestUtils { + + public static final String CLASS_A = "net.adoptopenjdk.integration.ClassA"; + + public static final String CLASS_B = "net.adoptopenjdk.integration.ClassB"; + + public static final String JAR_1 = "classloader-integration-tests-module-1.jar"; + + public static final String JAR_2 = "classloader-integration-tests-module-2.jar"; + + public static final String JAR_3 = "classloader-integration-tests-module-3.jar"; + + public static final String JAR_WITH_NATIVE = "classloader-integration-tests-module-native.jar"; + + private static final JNLPFileFactory JNLP_FILE_FACTORY = new JNLPFileFactory(); + + public static DummyPartsHandler createDummyPartsHandlerFor(final String name) throws IOException, ParseException { + final JNLPFile jnlpFile = createFile(name); + final List parts = createPartsFor(jnlpFile); + return new DummyPartsHandler(parts); + } + + public static List createPartsFor(final JNLPFile file) { + return createFor(file).getParts(); + } + + public static JNLPFile createFile(final String name) throws IOException, ParseException { + return JNLP_FILE_FACTORY.create(IntegrationTestResources.class.getResource(name)); + } + + public static PartExtractor createFor(final JNLPFile file) { + return new PartExtractor(file, JNLP_FILE_FACTORY); + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DownloadServiceFunctionalityTest.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DownloadServiceFunctionalityTest.java new file mode 100644 index 000000000..7cecf4a75 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DownloadServiceFunctionalityTest.java @@ -0,0 +1,106 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.Extension; +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import net.adoptopenjdk.icedteaweb.integration.IntegrationTestResources; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; + +import java.net.URL; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_A; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +public class DownloadServiceFunctionalityTest { + + @RepeatedTest(10) + public void testPartDownloaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + + //than + Assertions.assertFalse(partsHandler.isPartDownloaded("lazy-package")); + } + + @RepeatedTest(10) + public void testExtensionPartDownloaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-19.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + classLoader.loadClass(CLASS_A); + + //than + final URL extensionURL = IntegrationTestResources.load("integration-app-19-extension.jnlp"); + final Extension extension = new Extension(extensionURL, null); + Assertions.assertTrue(partsHandler.isPartDownloaded("lazy-package", extension)); + } + + @RepeatedTest(10) + public void testPartDownloaded2() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + classLoader.loadClass(CLASS_A); + + //than + Assertions.assertTrue(partsHandler.isPartDownloaded("lazy-package")); + } + + @RepeatedTest(10) + public void testDownloadPart() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-2.jnlp"); + + //when + partsHandler.downloadPart("lazy-package"); + + //than + Assertions.assertTrue(partsHandler.isPartDownloaded("lazy-package")); + } + + @RepeatedTest(10) + public void testEagerPart() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-21.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertTrue(partsHandler.isPartDownloaded("eager-package")); + } + + @RepeatedTest(10) + public void testDownloadPartFromExtension() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-19.jnlp"); + final URL extensionURL = IntegrationTestResources.load("integration-app-19-extension.jnlp"); + final Extension extension = new Extension(extensionURL, null); + + //when + partsHandler.downloadPart("lazy-package", extension); + + //than + Assertions.assertTrue(partsHandler.isPartDownloaded("lazy-package", extension)); + Assertions.assertFalse(partsHandler.isPartDownloaded("lazy-package")); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DummyPartsHandler.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DummyPartsHandler.java new file mode 100644 index 000000000..98160e1b0 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/DummyPartsHandler.java @@ -0,0 +1,54 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.Assert; +import net.adoptopenjdk.icedteaweb.classloader.ApplicationTrustValidator; +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.Part; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import net.adoptopenjdk.icedteaweb.jnlp.element.resource.JARDesc; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +public class DummyPartsHandler extends PartsHandler { + + public DummyPartsHandler(final List parts) { + super(parts, new DummyApplicationTrustValidator()); + } + + private final List downloaded = new CopyOnWriteArrayList<>(); + + @Override + protected Optional getLocalUrlForJar(final JARDesc jarDesc) { + Assert.requireNonNull(jarDesc, "jarDesc"); + if (downloaded.contains(jarDesc)) { + throw new IllegalStateException("Already downloaded " + jarDesc.getLocation()); + } + System.out.println("Should load " + jarDesc.getLocation()); + downloaded.add(jarDesc); + return Optional.ofNullable(jarDesc.getLocation()); + } + + public boolean hasTriedToDownload(final String name) { + return downloaded.stream() + .anyMatch(jar -> jar.getLocation().toString().endsWith(name)); + } + + public List getDownloaded() { + return Collections.unmodifiableList(downloaded); + } + + private static class DummyApplicationTrustValidator implements ApplicationTrustValidator { + @Override + public void validateEagerJars(List jars) { + } + + @Override + public void validateLazyJars(List jars) { + } + } + +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ExtensionSupportClassloaderTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ExtensionSupportClassloaderTests.java new file mode 100644 index 000000000..6058d4bbd --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/ExtensionSupportClassloaderTests.java @@ -0,0 +1,129 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_A; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_B; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_2; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +public class ExtensionSupportClassloaderTests { + + /** + * A part of an extension JNLP will not be automatically downloaded if all jars of the part are lazy + */ + @RepeatedTest(10) + public void testClassFromLazyJarNotInitialLoaded() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-19.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + } + + /** + * A lazy part of an extension JNLP will be downloaded if a class of the part is loaded + */ + @RepeatedTest(10) + public void testLoadClassFromLazyJar() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-19.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * An eager jar of an extension JNLP will automatically be downloaded + */ + @RepeatedTest(10) + public void testLoadClassFromEagerJar() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-20.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A class from an eager jar of an extension JNLP can be loaded + */ + @RepeatedTest(10) + public void testLoadClassFromEagerJar2() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-20.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * Parts with the same name in the main JNLP and an extension JNLP do not belong together + */ + @RepeatedTest(10) + public void testPartIsJnlpExclusive() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-22.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * Parts with the same name in the main JNLP and an extension JNLP do not belong together + */ + @RepeatedTest(10) + public void testPartIsJnlpExclusive2() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-22.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_B); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/JavaVersionSpecificClassloaderIntegrationTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/JavaVersionSpecificClassloaderIntegrationTests.java new file mode 100644 index 000000000..532ab98d1 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/JavaVersionSpecificClassloaderIntegrationTests.java @@ -0,0 +1,69 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_2; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +public class JavaVersionSpecificClassloaderIntegrationTests { + + /** + * Resources that are defined as part of a not matching Java version won't be loaded + */ + @RepeatedTest(10) + public void testNotLoadJarFromNotMatchingJavaVersion() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-9.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * Resources that are defined as part of a not matching Java version won't be loaded + */ + @RepeatedTest(10) + public void testNotLoadJarFromNotMatchingJavaVersion2() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-14.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * Resources that are defined as part of a matching Java version will be loaded + */ + @RepeatedTest(10) + public void testLoadJarFromMatchingJavaVersion() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-10.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/LocaleSpecificClassloaderIntegrationTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/LocaleSpecificClassloaderIntegrationTests.java new file mode 100644 index 000000000..a4f9cbe55 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/LocaleSpecificClassloaderIntegrationTests.java @@ -0,0 +1,73 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.Locale; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_2; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +@Execution(ExecutionMode.SAME_THREAD) +public class LocaleSpecificClassloaderIntegrationTests { + + private static Locale defaultLocale; + + @BeforeAll + public static void init() { + defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.GERMAN); + } + + @AfterAll + public static void end() { + Locale.setDefault(defaultLocale); + } + + /** + * Resources with a matching local will be loaded + */ + @RepeatedTest(10) + public void testLoadForConcreteLocale() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-12.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(2, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + /** + * Resources with a not matching local won't be loaded + */ + @RepeatedTest(10) + public void testNotLoadForWrongLocale() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-13.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_2)); + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/NativeSupportClassloaderIntegrationTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/NativeSupportClassloaderIntegrationTests.java new file mode 100644 index 000000000..6ae34448f --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/NativeSupportClassloaderIntegrationTests.java @@ -0,0 +1,143 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import net.adoptopenjdk.icedteaweb.xmlparser.ParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_WITH_NATIVE; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createFile; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createPartsFor; + +public class NativeSupportClassloaderIntegrationTests { + + private static final String NATIVE_CLASS = "net.adoptopenjdk.integration.ClassWithNativeCall"; + + /** + * A jar that is defined by nativelib tag will be downloaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void loadJarWithNativeContent() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-15.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_WITH_NATIVE)); + } + + /** + * A class in a jar that is defined by nativelib tag can be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void loadClassWithNativeMethod() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-15.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadClass = classLoader.loadClass(NATIVE_CLASS); + + //than + Assertions.assertNotNull(loadClass); + } + + /** + * A native method that native lib is part of a jar can be called + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void callNativeMethod() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-15.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadClass = classLoader.loadClass(NATIVE_CLASS); + final Object classInstance = loadClass.newInstance(); + final Object result = loadClass.getMethod("callNative").invoke(classInstance); + + //than + Assertions.assertNotNull(result); + Assertions.assertEquals("Hello from native world!", result); + } + + /** + * If the JNLP does not have a security environment but has nativelib parts the initialization will crash + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void doNotLoadNativeWithoutSecurityEnvironment() { + Assertions.assertThrows(ParseException.class, () -> createPartsFor(createFile("integration-app-16.jnlp"))); + } + + /** + * If a jar is defined as jar (and not as nativelib) in the JNLP than native libraries that are part of the jar can not be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void doNotLoadNativeForSimpleJarDesc() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-17.jnlp"); + + //than + Assertions.assertThrows(UnsatisfiedLinkError.class, () -> { + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + classLoader.loadClass(NATIVE_CLASS).newInstance(); + }); + } + + /** + * if a nativelib is defined as lazy than the content (with the native content) won't be downloaded automatically + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void doNotLoadLazyNativeLibAtStart() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-18.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + } + + /** + * if a nativelib is defined as lazy than the content (with the native content) will be loaded once a class from the + * lib will be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) // We only have native lib for MAC so far... + public void callNativeMethodFromLazyJar() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-18.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadClass = classLoader.loadClass(NATIVE_CLASS); + final Object classInstance = loadClass.newInstance(); + final Object result = loadClass.getMethod("callNative").invoke(classInstance); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_WITH_NATIVE)); + Assertions.assertNotNull(result); + Assertions.assertEquals("Hello from native world!", result); + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/OsSpecificClassloaderIntegrationTests.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/OsSpecificClassloaderIntegrationTests.java new file mode 100644 index 000000000..b9c3d8e79 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/classloader/OsSpecificClassloaderIntegrationTests.java @@ -0,0 +1,225 @@ +package net.adoptopenjdk.icedteaweb.integration.classloader; + +import net.adoptopenjdk.icedteaweb.classloader.JnlpApplicationClassLoader; +import net.adoptopenjdk.icedteaweb.classloader.PartsHandler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.CLASS_A; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_1; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_2; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.JAR_3; +import static net.adoptopenjdk.icedteaweb.integration.classloader.ClassloaderTestUtils.createDummyPartsHandlerFor; + +public class OsSpecificClassloaderIntegrationTests { + + /** + * A resource that has defined windows as os will be loaded on windows systems + */ + @RepeatedTest(10) + @EnabledOnOs(OS.WINDOWS) + public void testWindowsOnlyResourceOnWindows() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-4.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A resource that has defined windows as os will be loaded on windows systems and classes can be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.WINDOWS) + public void testWindowsOnlyResourceOnWindowsWithLoadClass() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-4.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + } + + /** + * A resource that has defined windows as os won't be loaded on other operation systems + */ + @RepeatedTest(10) + @DisabledOnOs(OS.WINDOWS) + public void testWindowsOnlyResourceOnNotWindows() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-4.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A resource that has defined mac as os will be loaded on mac systems + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) + public void testMacOnlyResourceOnMac() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-5.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A resource that has defined mac as os will be loaded on mac systems and classes can be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) + public void testMacOnlyResourceOnMacWithLoadClass() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-5.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + } + + /** + * A resource that has defined mac as os won't be loaded on other operation systems + */ + @RepeatedTest(10) + @DisabledOnOs(OS.MAC) + public void testMacOnlyResourceOnNotMac() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-5.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A resource that has defined linux as os will be loaded on linux systems + */ + @RepeatedTest(10) + @EnabledOnOs(OS.LINUX) + public void testLinuxOnlyResourceOnLinux() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-6.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + /** + * A resource that has defined linux as os will be loaded on linux systems and classes can be loaded + */ + @RepeatedTest(10) + @EnabledOnOs(OS.LINUX) + public void testLinuxOnlyResourceOnLinuxWithLoadClass() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-6.jnlp"); + + //when + final ClassLoader classLoader = createAndInitClassloader(partsHandler); + final Class loadedClass = classLoader.loadClass(CLASS_A); + + //than + Assertions.assertNotNull(loadedClass); + Assertions.assertEquals(classLoader, loadedClass.getClassLoader()); + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + } + + /** + * A resource that has defined linux as os won't be loaded on other operation systems + */ + @RepeatedTest(10) + @DisabledOnOs(OS.LINUX) + public void testLinuxOnlyResourceOnNotLinux() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-6.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(0, partsHandler.getDownloaded().size()); + Assertions.assertFalse(partsHandler.hasTriedToDownload(JAR_1)); + } + + @RepeatedTest(10) + @EnabledOnOs(OS.MAC) + public void testMacOnlyResourceInJreOnMac() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-24.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_3)); + } + + @RepeatedTest(10) + @EnabledOnOs(OS.WINDOWS) + public void testWindowsOnlyResourceInJreOnWindows() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-24.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_1)); + } + + @RepeatedTest(10) + @EnabledOnOs(OS.LINUX) + public void testLinuxOnlyResourceInJreOnLinux() throws Exception { + //given + final DummyPartsHandler partsHandler = createDummyPartsHandlerFor("integration-app-24.jnlp"); + + //when + createAndInitClassloader(partsHandler); + + //than + Assertions.assertEquals(1, partsHandler.getDownloaded().size()); + Assertions.assertTrue(partsHandler.hasTriedToDownload(JAR_2)); + } + + private ClassLoader createAndInitClassloader(PartsHandler partsHandler) { + final JnlpApplicationClassLoader classLoader = new JnlpApplicationClassLoader(partsHandler); + classLoader.initializeEagerJars(); + return classLoader; + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/signing/UnsignedJarsTest.java b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/signing/UnsignedJarsTest.java new file mode 100644 index 000000000..c0cb45d2d --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/java/net/adoptopenjdk/icedteaweb/integration/signing/UnsignedJarsTest.java @@ -0,0 +1,42 @@ +package net.adoptopenjdk.icedteaweb.integration.signing; + +import net.adoptopenjdk.icedteaweb.integration.DummyResourceTracker; +import net.adoptopenjdk.icedteaweb.integration.IntegrationTestResources; +import net.adoptopenjdk.icedteaweb.resources.ResourceTrackerFactory; +import net.adoptopenjdk.icedteaweb.security.SecurityUserInteractions; +import net.adoptopenjdk.icedteaweb.security.dialog.result.AllowDeny; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.JNLPFileFactory; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test with unsigned jars. + */ +@ExtendWith(MockitoExtension.class) +class UnsignedJarsTest { + + @Test + @Execution(ExecutionMode.SAME_THREAD) + @Disabled + void launchUnsignedApp(@Mock SecurityUserInteractions userInteractions) throws Exception { + final JNLPFile jnlpFile = new JNLPFileFactory().create(IntegrationTestResources.load("integration-app-25.jnlp")); + final ResourceTrackerFactory resourceTrackerFactory = new DummyResourceTracker.Factory(); + + when(userInteractions.askUserForPermissionToRunUnsignedApplication(jnlpFile)).thenReturn(AllowDeny.ALLOW); + + // when + final ThreadGroup threadGroup = new ThreadGroup("Test-Group"); + new ApplicationInstance(jnlpFile, resourceTrackerFactory, threadGroup); + verify(userInteractions).askUserForPermissionToRunUnsignedApplication(jnlpFile); + } +} diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-1.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-1.jnlp new file mode 100644 index 000000000..0ef78090f --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-1.jnlp @@ -0,0 +1,12 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-10.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-10.jnlp new file mode 100644 index 000000000..0038728cf --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-10.jnlp @@ -0,0 +1,17 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-11.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-11.jnlp new file mode 100644 index 000000000..14ae74192 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-11.jnlp @@ -0,0 +1,16 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-12.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-12.jnlp new file mode 100644 index 000000000..22fb556d0 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-12.jnlp @@ -0,0 +1,16 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-13.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-13.jnlp new file mode 100644 index 000000000..57e86fa1f --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-13.jnlp @@ -0,0 +1,16 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-14.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-14.jnlp new file mode 100644 index 000000000..79cde2086 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-14.jnlp @@ -0,0 +1,17 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-15.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-15.jnlp new file mode 100644 index 000000000..e598601e3 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-15.jnlp @@ -0,0 +1,15 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-16.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-16.jnlp new file mode 100644 index 000000000..e92a616f5 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-16.jnlp @@ -0,0 +1,12 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-17.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-17.jnlp new file mode 100644 index 000000000..26b3242e1 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-17.jnlp @@ -0,0 +1,15 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-18.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-18.jnlp new file mode 100644 index 000000000..9a8345345 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-18.jnlp @@ -0,0 +1,16 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19-extension.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19-extension.jnlp new file mode 100644 index 000000000..4dc761a1d --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19-extension.jnlp @@ -0,0 +1,11 @@ + + + + Extension + AdoptOpenJDK + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19.jnlp new file mode 100644 index 000000000..b6733a2b3 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-19.jnlp @@ -0,0 +1,12 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-2.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-2.jnlp new file mode 100644 index 000000000..23e1f2c24 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-2.jnlp @@ -0,0 +1,13 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20-extension.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20-extension.jnlp new file mode 100644 index 000000000..1a4b79637 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20-extension.jnlp @@ -0,0 +1,10 @@ + + + + Extension + AdoptOpenJDK + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20.jnlp new file mode 100644 index 000000000..aea12b0bf --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-20.jnlp @@ -0,0 +1,12 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-21.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-21.jnlp new file mode 100644 index 000000000..149b87be0 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-21.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22-extension.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22-extension.jnlp new file mode 100644 index 000000000..f1f61123e --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22-extension.jnlp @@ -0,0 +1,11 @@ + + + + Extension + AdoptOpenJDK + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22.jnlp new file mode 100644 index 000000000..bd13de75e --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-22.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-23.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-23.jnlp new file mode 100644 index 000000000..225a62f26 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-23.jnlp @@ -0,0 +1,15 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-24.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-24.jnlp new file mode 100644 index 000000000..9f16e6b42 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-24.jnlp @@ -0,0 +1,21 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-25.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-25.jnlp new file mode 100644 index 000000000..36e597c74 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-25.jnlp @@ -0,0 +1,15 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-3.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-3.jnlp new file mode 100644 index 000000000..68537f236 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-3.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-4.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-4.jnlp new file mode 100644 index 000000000..b3dac2749 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-4.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-5.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-5.jnlp new file mode 100644 index 000000000..ee3410c0f --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-5.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-6.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-6.jnlp new file mode 100644 index 000000000..fd6f33151 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-6.jnlp @@ -0,0 +1,14 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-7.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-7.jnlp new file mode 100644 index 000000000..f51bbed30 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-7.jnlp @@ -0,0 +1,13 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-8.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-8.jnlp new file mode 100644 index 000000000..2d523421c --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-8.jnlp @@ -0,0 +1,13 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + diff --git a/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-9.jnlp b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-9.jnlp new file mode 100644 index 000000000..33c9e8766 --- /dev/null +++ b/integration-tests/classloader-integration-tests/src/test/resources/net/adoptopenjdk/icedteaweb/integration/integration-app-9.jnlp @@ -0,0 +1,17 @@ + + + + IcedTeaWeb Integration Test 1 + AdoptOpenJDK + + + + + + + + + + + + diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml new file mode 100644 index 000000000..6dab43129 --- /dev/null +++ b/integration-tests/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + net.adoptopenjdk + icedtea-web-parent + 3.0.0-SNAPSHOT + ../ + + + integration-tests + pom + + + classloader-integration-tests-module-1 + classloader-integration-tests-module-2 + classloader-integration-tests-module-3 + classloader-integration-tests-module-native + classloader-integration-tests-module-wrapper + classloader-integration-tests + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + false + + + + + + + diff --git a/jnlp-api/pom.xml b/jnlp-api/pom.xml index d7dc0978a..6e5f91d48 100644 --- a/jnlp-api/pom.xml +++ b/jnlp-api/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ diff --git a/jnlp-api/src/main/java/javax/jnlp/BasicService.java b/jnlp-api/src/main/java/javax/jnlp/BasicService.java index b04691e8e..e9e3e277c 100644 --- a/jnlp-api/src/main/java/javax/jnlp/BasicService.java +++ b/jnlp-api/src/main/java/javax/jnlp/BasicService.java @@ -1,13 +1,47 @@ package javax.jnlp; -public interface BasicService { +import java.net.URL; - public java.net.URL getCodeBase(); +/** + * The BasicService interface provides access to the codebase of the application, if an application is run + * in offline mode, and simple interaction with the native browser on the given platform. + *

    + * This interface mimics loosely the AppletContext functionality. + */ +public interface BasicService { - public boolean isOffline(); + /** + * Returns the codebase for the application. The codebase is either specified directly in the JNLP file, + * or it is the location of the JAR file containing the main class of the application. + * + * @return a URL with the codebase of the application, or null if the application is running from + * local file system. + */ + URL getCodeBase(); - public boolean showDocument(java.net.URL url); + /** + * Determines if the system is offline. The return value represents the JNLP client's "best guess" at the + * online / offline state of the client system. The return value is does not have to be guaranteed to + * be reliable, as it is sometimes difficult to ascertain the true online / offline state of a client system. + * + * @return true if the system is offline, otherwise false + */ + boolean isOffline(); - public boolean isWebBrowserSupported(); + /** + * Directs a browser on the client to show the given URL. This will typically replace the page currently + * being viewed in a browser with the given URL, or cause a browser to be launched that will show the given URL. + * + * @param url an URL giving the location of the document. A relative URL will be relative to the codebase. + * @return true if the request succeeded false if the url is null or the request failed. + */ + boolean showDocument(URL url); + /** + * Checks if a Web browser is supported on the current platform and by the given JNLP Client. + * If this is not the case, then showDocument(java.net.URL) will always return false. + * + * @return true if a Web browser is supported, otherwise false + */ + boolean isWebBrowserSupported(); } diff --git a/jnlp-api/src/main/java/javax/jnlp/ClipboardService.java b/jnlp-api/src/main/java/javax/jnlp/ClipboardService.java index 3d3696fe1..a939c993c 100644 --- a/jnlp-api/src/main/java/javax/jnlp/ClipboardService.java +++ b/jnlp-api/src/main/java/javax/jnlp/ClipboardService.java @@ -1,9 +1,31 @@ package javax.jnlp; +import java.awt.datatransfer.Transferable; + +/** + * ClipboardService provides methods for accessing the shared system-wide clipboard, + * even for applications that are running in the untrusted execution environment. + * Implementors should warn the user of the potential security risk of letting an + * untrusted application have access to potentially confidential information stored + * in the clipboard, or overwriting the contents of the clipboard. + * + * @since 1.4.2 + */ public interface ClipboardService { - public java.awt.datatransfer.Transferable getContents(); + /** + * Returns a Transferable object representing the current contents of the clipboard. + * If the clipboard currently has no contents, it returns null. + * + * @return The current Transferable object on the clipboard. + */ + Transferable getContents(); - public void setContents(java.awt.datatransfer.Transferable contents); + /** + * Sets the current contents of the clipboard to the specified Transferable object. + * + * @param contents The Transferable object representing clipboard content. + */ + void setContents(Transferable contents); } diff --git a/jnlp-api/src/main/java/javax/jnlp/DownloadService.java b/jnlp-api/src/main/java/javax/jnlp/DownloadService.java index 558b9b4c1..51fbb6a5b 100644 --- a/jnlp-api/src/main/java/javax/jnlp/DownloadService.java +++ b/jnlp-api/src/main/java/javax/jnlp/DownloadService.java @@ -1,37 +1,180 @@ package javax.jnlp; +import java.net.URL; + +/** + * DownloadService service allows an application to control how its own resources are cached, + * to determine which of its resources are currently cached, to force resources to be cached, + * and to remove resources from the cache. + * The JNLP Client is responsible for providing a specific implementation of this service. + * + * @since 1.4.2 + */ public interface DownloadService { - public boolean isResourceCached(java.net.URL ref, java.lang.String version); - - public boolean isPartCached(java.lang.String part); - - public boolean isPartCached(java.lang.String[] parts); - - public boolean isExtensionPartCached(java.net.URL ref, java.lang.String version, java.lang.String part); - - public boolean isExtensionPartCached(java.net.URL ref, java.lang.String version, java.lang.String[] parts); - - public void loadResource(java.net.URL ref, java.lang.String version, DownloadServiceListener progress) throws java.io.IOException; - - public void loadPart(java.lang.String part, DownloadServiceListener progress) throws java.io.IOException; - - public void loadPart(java.lang.String[] parts, DownloadServiceListener progress) throws java.io.IOException; - - public void loadExtensionPart(java.net.URL ref, java.lang.String version, java.lang.String part, DownloadServiceListener progress) throws java.io.IOException; - - public void loadExtensionPart(java.net.URL ref, java.lang.String version, java.lang.String[] parts, DownloadServiceListener progress) throws java.io.IOException; - - public void removeResource(java.net.URL ref, java.lang.String version) throws java.io.IOException; - - public void removePart(java.lang.String part) throws java.io.IOException; - - public void removePart(java.lang.String[] parts) throws java.io.IOException; - - public void removeExtensionPart(java.net.URL ref, java.lang.String version, java.lang.String part) throws java.io.IOException; - - public void removeExtensionPart(java.net.URL ref, java.lang.String version, java.lang.String[] parts) throws java.io.IOException; - - public DownloadServiceListener getDefaultProgressWindow(); + /** + * Returns true if the resource referred to by the given URL and version is cached, and that resource + * is either mentioned in the calling applications JNLP file, is within the codebase of the calling + * applications JNLP file, or the calling application has been granted all-permissions. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @return true if the above conditions are met, and false otherwise. + */ + boolean isResourceCached(URL ref, String version); + + /** + * Returns true if the part referred to by the given string is cached, and that part is mentioned in the JNLP file for the application. + * + * @param part The name of the part. + * @return true if the above conditions are met, and false otherwise. + */ + boolean isPartCached(String part); + + /** + * Returns true if the parts referred to by the given array are cached, and those parts are mentioned in the JNLP file for the application. + * + * @param parts An array of part names. + * @return true if the above conditions are met, and false otherwise. + */ + boolean isPartCached(String[] parts); + + /** + * Returns true if the given part of the given extension is cached, and the extension and part + * are mentioned in the JNLP file for the application. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param part The name of the part. + * @return true if the above conditions are met, and false otherwise. + */ + boolean isExtensionPartCached(URL ref, String version, String part); + + /** + * Returns true if the given parts of the given extension are cached, and the extension and parts are + * mentioned in the JNLP file for the application. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param parts An array of part names. + * @return true if the above conditions are met, and false otherwise. + */ + boolean isExtensionPartCached(URL ref, String version, String[] parts); + + /** + * Downloads the given resource, if the resource is either mentioned in the calling applications JNLP file, + * is within the codebase of the calling applications JNLP file, or if the calling application has been + * granted all-permissions. This method will block until the download is completed or an exception occurs. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param progress Download progress callback object. + * @throws java.io.IOException if an I/O error occurs + */ + void loadResource(URL ref, String version, DownloadServiceListener progress) throws java.io.IOException; + + /** + * Downloads the given part, if the part is mentioned in the JNLP file for the application. + * This method will block until the download is completed or an exception occurs. + * + * @param part The name of the part. + * @param progress Download progress callback object. + * @throws java.io.IOException if an I/O error occurs + */ + void loadPart(String part, DownloadServiceListener progress) throws java.io.IOException; + + /** + * Downloads the given parts, if the parts are mentioned in the JNLP file for the application. + * This method will block until the download is completed or an exception occurs. + * + * @param parts An array of part names. + * @param progress Download progress callback object. + * @throws java.io.IOException if an I/O error occurs + */ + void loadPart(String[] parts, DownloadServiceListener progress) throws java.io.IOException; + + /** + * Downloads the given part of the given extension, + * if the part and the extension are mentioned in the JNLP file for the application. + * This method will block until the download is completed or an exception occurs. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param part The name of the part. + * @param progress Download progress callback object. + * @throws java.io.IOException if an I/O error occurs + */ + void loadExtensionPart(URL ref, String version, String part, DownloadServiceListener progress) throws java.io.IOException; + + /** + * Downloads the given parts of the given extension, + * if the parts and the extension are mentioned in the JNLP file for the application. + * This method will block until the download is completed or an exception occurs. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param parts An array of part names to load. + * @param progress Download progress callback object. + * @throws java.io.IOException if an I/O error occurs + */ + void loadExtensionPart(URL ref, String version, String[] parts, DownloadServiceListener progress) throws java.io.IOException; + + /** + * Removes the given resource from the cache, + * if the resource is either mentioned in the calling applications JNLP file, + * is within the codebase of the calling applications JNLP file, + * or if the calling application has been granted all-permissions. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @throws java.io.IOException if an I/O error occurs + */ + void removeResource(URL ref, String version) throws java.io.IOException; + + /** + * Removes the given part from the cache, if the part is mentioned in the JNLP file for the application. + * + * @param part The name of the part. + * @throws java.io.IOException if an I/O error occurs + */ + void removePart(String part) throws java.io.IOException; + + /** + * Removes the given parts from the cache, if the parts are mentioned in the JNLP file for the application. + * + * @param parts An array of part names. + * @throws java.io.IOException if an I/O error occurs + */ + void removePart(String[] parts) throws java.io.IOException; + + /** + * Removes the given part of the given extension from the cache, + * if the part and the extension are mentioned in the JNLP file for the application. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param part The name of the part. + * @throws java.io.IOException if an I/O error occurs + */ + void removeExtensionPart(URL ref, String version, String part) throws java.io.IOException; + + /** + * Removes the given parts of the given extension from the cache, + * if the parts and the extension are mentioned in the JNLP file for the application. + * + * @param ref The URL for the resource. + * @param version The version string, or null for no version. + * @param parts An array of part names. + * @throws java.io.IOException if an I/O error occurs + */ + void removeExtensionPart(URL ref, String version, String[] parts) throws java.io.IOException; + + /** + * Return a default DownloadServiceListener implementation which, when passed to a load method, + * should pop up and update a progress window as the load progresses. + * + * @return A DownloadServiceListener object representing a download progress listener. + */ + DownloadServiceListener getDefaultProgressWindow(); } diff --git a/jnlp-api/src/main/java/javax/jnlp/DownloadService2.java b/jnlp-api/src/main/java/javax/jnlp/DownloadService2.java index 1d359fbeb..f373a9f54 100644 --- a/jnlp-api/src/main/java/javax/jnlp/DownloadService2.java +++ b/jnlp-api/src/main/java/javax/jnlp/DownloadService2.java @@ -1,8 +1,23 @@ package javax.jnlp; +import java.io.IOException; + +/** + * Provides cache query services to JNLP applications. + * Together with methods in DownloadService, this allows for advanced programmatic cache management. + * + * @since 6.0.18 + */ public interface DownloadService2 { - public static class ResourceSpec { + /** + * Specifies patterns for resource queries as arguments and holds results in + * {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + * For the url and version properties, standard regular expressions as documented in + * {code java.util.regex} are supported. + */ + class ResourceSpec { public static final long UNKNOWN = Long.MIN_VALUE; @@ -10,48 +25,151 @@ public static class ResourceSpec { protected String version; protected int type; + /** + * Creates a new ResourceSpec instance. + * + * @param url the URL pattern + * @param version the version pattern + * @param type the resource type. This should be one of the following constants defined in DownloadService2: + * {@link #ALL}, {@link #APPLICATION}, {@link #APPLET}, {@link #EXTENSION}, + * {@link #JAR}, {@link #IMAGE}, or {@link #CLASS}. + */ public ResourceSpec(java.lang.String url, java.lang.String version, int type) { this.url = url; this.version = version; this.type = type; } + /** + * Returns the time of expiration of the resource. + * The returned value has the same semantics as the return value of System.currentTimeMillis(). + * A value of 0 means unknown. + * + * @return the time of expiration of the resource + */ public long getExpirationDate() { return UNKNOWN; } + /** + * Returns the time of last modification of the resource. + * The returned value has the same semantics as the return value of System.currentTimeMillis(). + * A value of 0 means unknown. + * + * @return the time of last modification of the resource + */ public long getLastModified() { return UNKNOWN; } + /** + * Returns the size of a resource. + * This is only useful for ResourceSpecs that have been returned as a result of + * {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} or + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + * + * @return the size of a resource + */ public long getSize() { return UNKNOWN; } + /** + * Returns the type of this resource. + * + * @return the type of this resource + */ public int getType() { return type; } + /** + * Returns the URL of this resource. + * + * @return the URL of this resource + */ public java.lang.String getUrl() { return url; } + /** + * Returns the version of this resource. + * + * @return the version of this resource + */ public java.lang.String getVersion() { return version; } } - public static final int ALL = 0; - public static final int APPLET = 2; - public static final int APPLICATION = 1; - public static final int CLASS = 6; - public static final int EXTENSION = 3; - public static final int IMAGE = 5; - public static final int JAR = 4; + /** + * Matches all resources in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int ALL = 0; + /** + * Matches applets in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int APPLET = 2; + /** + * Matches applications in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int APPLICATION = 1; + /** + * Matches class files in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int CLASS = 6; + /** + * Matches extensions in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int EXTENSION = 3; + /** + * Matches images in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int IMAGE = 5; + /** + * Matches JARs in {@link #getCachedResources(javax.jnlp.DownloadService2.ResourceSpec)} and + * {@link #getUpdateAvailableResources(javax.jnlp.DownloadService2.ResourceSpec)}. + */ + int JAR = 4; - public DownloadService2.ResourceSpec[] getCachedResources( - javax.jnlp.DownloadService2.ResourceSpec resourceSpec); + /** + * Returns all resources in the cache that match one of the specified resource specs. + * For supported patterns in the query arguments, see DownloadService2.ResourceSpec. + * The returned ResourceSpec objects have specific URL and version properties (i.e. no patterns). + * + * @param resourceSpec the spec to match resources against + * @return all resources that match one of the specs + * @throws IllegalArgumentException if the ResourceSpec is null, or + * if the ResourceSpec contains a null or empty URL string, or + * if the ResourceSpec contains invalid regular expressions. + * if the ResourceSpec contains a type that is not one of: + * {@link #ALL}, {@link #APPLICATION}, {@link #APPLET}, {@link #EXTENSION}, + * {@link #JAR}, {@link #IMAGE}, or {@link #CLASS}. + */ + DownloadService2.ResourceSpec[] getCachedResources(ResourceSpec resourceSpec); - public DownloadService2.ResourceSpec[] getUpdateAvailableResources( - javax.jnlp.DownloadService2.ResourceSpec resourceSpec); + /** + * Returns all resources in the cache that match one of the specified resource specs AND + * have an update available from their server. + * For supported patterns in the query arguments, see DownloadService2.ResourceSpec. + * The returned ResourceSpec objects have specific URL and version properties (i.e. no patterns). + * NOTE: This call may attempt HTTP GET request to check for update. + * + * @param resourceSpec the spec to match resources against + * @return all resources for which an update is available that match one of the specs + * @throws IOException if something went wrong during update checks + * @throws IllegalArgumentException if the ResourceSpec is null, or + * if the ResourceSpec contains a null or empty URL string, or + * if the ResourceSpec contains invalid regular expressions. + * if the ResourceSpec contains a type that is not one of: + * {@link #ALL}, {@link #APPLICATION}, {@link #APPLET}, {@link #EXTENSION}, + * {@link #JAR}, {@link #IMAGE}, or {@link #CLASS}. + */ + DownloadService2.ResourceSpec[] getUpdateAvailableResources(ResourceSpec resourceSpec) throws IOException; } diff --git a/jnlp-api/src/main/java/javax/jnlp/DownloadServiceListener.java b/jnlp-api/src/main/java/javax/jnlp/DownloadServiceListener.java index 519161f45..3bd3f5ed8 100644 --- a/jnlp-api/src/main/java/javax/jnlp/DownloadServiceListener.java +++ b/jnlp-api/src/main/java/javax/jnlp/DownloadServiceListener.java @@ -1,13 +1,69 @@ package javax.jnlp; -public interface DownloadServiceListener { +import java.net.URL; - public void progress(java.net.URL url, java.lang.String version, long readSoFar, long total, int overallPercent); +/** + * The DownloadServiceListener provides an interface for a callback object implementation, + * which may be used by a DownloadService implementation. + * The DownloadServiceListener implementation's methods should be invoked by the + * DownloadService implementation at various stages of the download, + * allowing an application that uses the JNLP API to display a progress bar during a DownloadService download. + * + * @ee {@link DownloadService} + * @since 1.4.2 + */ +public interface DownloadServiceListener { - public void validating(java.net.URL url, java.lang.String version, long entry, long total, int overallPercent); + /** + * A JNLP client's DownloadService implementation should call this method several times during a download. + * A DownloadServiceListener implementation may display a progress bar and / or update information based + * on the parameters. + * + * @param url The URL representing the resource being downloaded. + * @param version The version of the resource being downloaded. + * @param readSoFar The number of bytes downloaded so far. + * @param total The total number of bytes to be downloaded, or -1 if the number is unknown. + * @param overallPercent The percentage of the overall update operation that is complete, + * or -1 if the percentage is unknown. + */ + void progress(URL url, String version, long readSoFar, long total, int overallPercent); - public void upgradingArchive(java.net.URL url, java.lang.String version, int patchPercent, int overallPercent); + /** + * A JNLP client's DownloadService implementation should call this method at least several times during + * validation of a download. Validation often includes ensuring that downloaded resources are authentic + * (appropriately signed). A DownloadServiceListener implementation may display a progress bar and / or + * update information based on the parameters. + * + * @param url The URL representing the resource being validated. + * @param version The version of the resource being validated. + * @param entry The number of JAR entries validated so far. + * @param total The total number of entries to be validated. + * @param overallPercent The percentage of the overall update operation that is complete, + * or -1 if the percentage is unknown. + */ + void validating(URL url, String version, long entry, long total, int overallPercent); - public void downloadFailed(java.net.URL url, java.lang.String version); + /** + * A JNLP client's DownloadService implementation should call this method at least several times when + * applying an incremental update to an in-cache resource. A DownloadServiceListener implementation may + * display a progress bar and / or update information based on the parameters. + * + * @param url The URL representing the resource being patched. + * @param version The version of the resource being patched. + * @param patchPercent The percentage of the patch operation that is complete, + * or -1 if the percentage is unknown. + * @param overallPercent The percentage of the overall update operation that is complete, + * or -1 if the percentage is unknown. + */ + void upgradingArchive(URL url, String version, int patchPercent, int overallPercent); + /** + * A JNLP client's DownloadService implementation should call this method if a download fails or + * aborts unexpectedly. In response, a DownloadServiceListener implementation may display update + * information to the user to reflect this. + * + * @param url The URL representing the resource for which the download failed. + * @param version The version of the resource for which the download failed. + */ + void downloadFailed(URL url, String version); } diff --git a/jnlp-api/src/main/java/javax/jnlp/ExtendedService.java b/jnlp-api/src/main/java/javax/jnlp/ExtendedService.java index 9e06e7dd5..3b3b7c771 100644 --- a/jnlp-api/src/main/java/javax/jnlp/ExtendedService.java +++ b/jnlp-api/src/main/java/javax/jnlp/ExtendedService.java @@ -20,31 +20,34 @@ import java.io.IOException; /** - * This interface provides a way for the JNLP application to open specific files - * in the client's system. It asks permission from the user before opening any - * files. + * ExtendedService provides additional support to the current JNLP API, + * to allow applications to open a specific file or files in the client's file system. * - * @author Omair Majid + * @since 1.5 */ public interface ExtendedService { /** - * Open a file on the client' system and return its contents. The user must - * grant permission to the application for this to work. + * Allows the application to open the specified file, even if the application is running in the + * untrusted execution environment. If the application would not otherwise have permission to + * access the file, the JNLP CLient should warn user of the potential security risk. + * The contents of the file is returned as a FileContents object. * - * @param file the file to open - * @return the opened file as a {@link FileContents} object - * @throws IOException on any io problems + * @param file the file object + * @return A FileContents object with information about the opened file + * @throws IOException if there is any I/O error */ FileContents openFile(File file) throws IOException; /** - * Opens multiple files on the user's system and returns their contents as a - * {@link FileContents} array + * Allows the application to open the specified files, even if the application is running in the + * untrusted execution environment. If the application would not otherwise have permission to + * access the files, the JNLP CLient should warn user of the potential security risk. + * The contents of each file is returned as a FileContents object in the FileContents array. * - * @param files the files to open - * @return an array of FileContents objects - * @throws IOException on any io problems + * @param files the array of files + * @return A FileContents[] object with information about each opened file + * @throws IOException if there is any I/O error */ FileContents[] openFiles(File[] files) throws IOException; } diff --git a/jnlp-api/src/main/java/javax/jnlp/ExtensionInstallerService.java b/jnlp-api/src/main/java/javax/jnlp/ExtensionInstallerService.java index d96816720..eb56ad8ea 100644 --- a/jnlp-api/src/main/java/javax/jnlp/ExtensionInstallerService.java +++ b/jnlp-api/src/main/java/javax/jnlp/ExtensionInstallerService.java @@ -1,31 +1,121 @@ package javax.jnlp; -public interface ExtensionInstallerService { +import java.net.URL; - public java.lang.String getInstallPath(); +/** + * The ExtensionInstallerService is used by an extension installer to communicate with the JNLP Client. + * It provides the following type of functionality: + *

      + *
    • Access to prefered installation location, and other information about the JNLP Client
    • + *
    • Manipulation of the JNLP Client's download screen
    • + *
    • Methods for updating the JNLP Client with the installed code
    • + *
    + *

    + * The normal sequence of events for an installer is: + *

      + *
    1. Get service using ServiceManager.lookup("javax.jnlp.ExtensionInstallerService").
    2. + *
    3. Update status, heading, and progress as install progresses (setStatus, setHeading and updateProgress).
    4. + *
    5. Invoke either setJREInfo or setNativeLibraryInfo depending on if a JRE or a library is installed
    6. + *
    7. If successful invoke installSucceeded, otherwise invoke installFailed.
    8. + *
    + * + * @since 1.4.2 + */ +public interface ExtensionInstallerService { - public java.lang.String getExtensionVersion(); + /** + * Returns the directory where the installer is recommended to install the extension in. + * It is not required that the installer install in this directory, this is merely a suggested path. + * + * @return the directory where the installer is recommended to install the extension in. + */ + String getInstallPath(); - public java.net.URL getExtensionLocation(); + /** + * Returns the version of the extension being installed. + * + * @return the version of the extension being installed + */ + String getExtensionVersion(); - public void hideProgressBar(); + /** + * Returns the location of the extension being installed. + * + * @return the location of the extension being installed + */ + URL getExtensionLocation(); - public void hideStatusWindow(); + /** + * Hides the progress bar. + * Any subsequent calls to updateProgress will force it to be visible. + */ + void hideProgressBar(); - public void setHeading(java.lang.String heading); + /** + * Hides the status window. + * You should only invoke this if you are going to provide your own feedback + * to the user as to the progress of the install. + */ + void hideStatusWindow(); - public void setStatus(java.lang.String status); + /** + * Updates the heading text of the progress window. + * + * @param heading the heading text + */ + void setHeading(String heading); - public void updateProgress(int value); + /** + * Updates the status text of the progress window. + * + * @param status the status text + */ + void setStatus(String status); - public void installSucceeded(boolean needsReboot); + /** + * Updates the progress bar. + * + * @param value progress bar value - should be between 0 and 100. + */ + void updateProgress(int value); - public void installFailed(); + /** + * Installers should invoke this upon a successful installation of the extension. + * This will cause the JNLP Client to regain control and continue its normal operation. + * + * @param needsReboot If true, a reboot is needed + */ + void installSucceeded(boolean needsReboot); - public void setJREInfo(java.lang.String platformVersion, java.lang.String jrePath); + /** + * This should be invoked if the install fails. + * The JNLP Client will continue its operation, and inform the user that the install has failed. + */ + void installFailed(); - public void setNativeLibraryInfo(java.lang.String path); + /** + * Informs the JNLP Client of the path to the executable for the JRE, + * if this is an installer for a JRE, and about platform-version this JRE implements. + * + * @param platformVersion the platform-version this JRE implements + * @param jrePath the path to the executable for the JRE + */ + void setJREInfo(String platformVersion, String jrePath); - public java.lang.String getInstalledJRE(java.net.URL url, java.lang.String version); + /** + * Informs the JNLP Client of a directory where it should search for native libraries. + * + * @param path the search path for native libraries + */ + void setNativeLibraryInfo(String path); + /** + * Returns the path to the executable for the given JRE. + * This method can be used by extensions that needs to find information in a given JRE, or enhance a given JRE. + * + * @param url product location of the JRE + * @param version product version of the JRE + * @return The path to the executable for the given JRE, or null if the JRE is not installed. + */ + String getInstalledJRE(URL url, String version); } diff --git a/jnlp-api/src/main/java/javax/jnlp/FileContents.java b/jnlp-api/src/main/java/javax/jnlp/FileContents.java index 4658dff75..6903b49e6 100644 --- a/jnlp-api/src/main/java/javax/jnlp/FileContents.java +++ b/jnlp-api/src/main/java/javax/jnlp/FileContents.java @@ -1,23 +1,103 @@ package javax.jnlp; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * FileContents objects encapsulate the name and contents of a file. + * An implementation of this class is used by the FileOpenService, FileSaveService, and PersistenceService. + *

    + * The FileContents implementation returned by {@link PersistenceService#get(java.net.URL)}, + * FileOpenService, and FileSaveService should never truncate a file if the maximum file length + * is set to be less that the current file length. + * + * @see {@link FileOpenService}, {@link FileSaveService} + * @since 1.4.2 + */ public interface FileContents { - public java.lang.String getName() throws java.io.IOException; + /** + * Gets the file name as a String. + * + * @return a string containing the file name. + * @throws IOException if an I/O exception occurs. + */ + String getName() throws IOException; - public java.io.InputStream getInputStream() throws java.io.IOException; + /** + * Gets an InputStream from the file. + * + * @return an InputStream to the file. + * @throws IOException if an I/O exception occurs. + */ + InputStream getInputStream() throws IOException; - public java.io.OutputStream getOutputStream(boolean overwrite) throws java.io.IOException; + /** + * Gets an OutputStream to the file. + * A JNLP client may implement this interface to return an OutputStream subclass + * which restricts the amount of data that can be written to the stream. + * + * @param overwrite if true, then bytes will be written to the beginning of the file rather than the end + * @return an OutputStream from the file. + * @throws IOException if an I/O exception occurs. + */ + OutputStream getOutputStream(boolean overwrite) throws IOException; - public long getLength() throws java.io.IOException; + /** + * Gets the length of the file. + * + * @return the length of the file as a long. + * @throws IOException if an I/O exception occurs. + */ + long getLength() throws IOException; - public boolean canRead() throws java.io.IOException; + /** + * Returns whether the file can be read. + * + * @return true if the file can be read, false otherwise. + * @throws IOException if an I/O exception occurs. + */ + boolean canRead() throws IOException; - public boolean canWrite() throws java.io.IOException; + /** + * Returns whether the file can be written to. + * + * @return true if the file can be read, false otherwise. + * @throws IOException if an I/O exception occurs. + */ + boolean canWrite() throws IOException; - public JNLPRandomAccessFile getRandomAccessFile(java.lang.String mode) throws java.io.IOException; + /** + * Returns a JNLPRandomAccessFile representing a random access interface to the file's contents. + * The mode argument must either be equal to "r" or "rw", indicating the file is to be opened + * for input only or for both input and output, respectively. + * An IllegalArgumentException will be thrown if the mode is not equal to "r" or "rw". + * + * @param mode the access mode. + * @return a JNLPRandomAccessFile. + * @throws IOException if an I/O exception occurs. + */ + JNLPRandomAccessFile getRandomAccessFile(String mode) throws IOException; - public long getMaxLength() throws java.io.IOException; + /** + * Gets the maximum file length for the file, as set by the creator of this object. + * + * @return the maximum length of the file. + * @throws IOException if an I/O exception occurs. + */ + long getMaxLength() throws IOException; - public long setMaxLength(long maxlength) throws java.io.IOException; + /** + * Sets the maximum file length for the file. + * A JNLP client may enforce restrictions on setting the maximum file length. + * A JNLP client should not truncate a file if the maximum file length is set that is less + * than the current file size, but it also should not allow further writes to that file. + * + * @param maxlength the requested new maximum file length. + * @return the maximum file length that was granted. + * @throws IOException if an I/O exception occurs. + */ + long setMaxLength(long maxlength) throws IOException; } diff --git a/jnlp-api/src/main/java/javax/jnlp/FileOpenService.java b/jnlp-api/src/main/java/javax/jnlp/FileOpenService.java index d31fd35c5..9f9c65e25 100644 --- a/jnlp-api/src/main/java/javax/jnlp/FileOpenService.java +++ b/jnlp-api/src/main/java/javax/jnlp/FileOpenService.java @@ -1,9 +1,50 @@ package javax.jnlp; +import java.io.IOException; + +/** + * FileOpenService service allows the user to choose a file from the local file system, + * even for applications that are running in the untrusted execution environment. + * The JNLP Client is the mediator and is therefore responsible for providing the + * specific implementation of this, if any. + *

    + * This service provides a similar function as the file input field for HTML-based forms. + * + * @see {@link FileSaveService}, {@link FileContents} + * @since 1.4.2 + */ public interface FileOpenService { - public FileContents openFileDialog(java.lang.String pathHint, java.lang.String[] extensions) throws java.io.IOException; + /** + * Asks the user to choose a single file. + * The contents of a selected file is returned as a FileContents object. + * The returned FileContents object contains the contents along with the name of the file. + * The full path is not returned. + * + * @param pathHint A hint from the application to the initial directory for the file chooser. + * This might be ignored by the JNLP Client. + * @param extensions A list of default extensions to show in the file chooser. + * For example, String[] { "txt", "java" }. This might be ignored by the JNLP Client. + * @return A FileContent object with information about the chosen file, or null if the user did not choose a file. + * @throws IOException if the request failed in any way other than the user did not choose to select a file. + */ + FileContents openFileDialog(String pathHint, String[] extensions) throws IOException; - public FileContents[] openMultiFileDialog(java.lang.String pathHint, java.lang.String[] extensions) throws java.io.IOException; + /** + * Asks the user to choose one or more file. + * The contents of selected files is returned as an array of FileContents objects. + * The returned FileContents objects contain the contents along with the name of the file. + * The full path is not returned. + * + * @param pathHint A hint from the application to the initial directory for the file chooser. + * This might be ignored by the JNLP Client. + * @param extensions A list of default extensions to show in the file chooser. + * For example, String[] { "txt", "java" }. + * This might be ignored by the JNLP Client. + * @return An array of FileContent objects with information about the chosen files, + * or null if the user did not choose a file. + * @throws IOException if the request failed in any way other than the user did not choose to select a file. + */ + FileContents[] openMultiFileDialog(String pathHint, String[] extensions) throws IOException; } diff --git a/jnlp-api/src/main/java/javax/jnlp/FileSaveService.java b/jnlp-api/src/main/java/javax/jnlp/FileSaveService.java index d27201c5a..7389bf21e 100644 --- a/jnlp-api/src/main/java/javax/jnlp/FileSaveService.java +++ b/jnlp-api/src/main/java/javax/jnlp/FileSaveService.java @@ -1,9 +1,49 @@ package javax.jnlp; +import java.io.IOException; +import java.io.InputStream; + +/** + * FileSaveService service allows the user to save a file to the local file system, + * even for applications that are running in the untrusted execution environment. + * The JNLP Client is the mediator and is therefore responsible for providing the + * specific implementation of this, if any. + *

    + * This service provides similar functionality as the Save as... functionality provided by most browsers. + * + * @see {@link FileOpenService}, {@link FileContents} + * @since 1.4.2 + */ public interface FileSaveService { - public FileContents saveFileDialog(java.lang.String pathHint, java.lang.String[] extensions, java.io.InputStream stream, java.lang.String name) throws java.io.IOException; + /** + * Asks the users to save a file. + * + * @param pathHint A hint from the application to the default directory to be used. + * This might be ignored by the JNLP Client. + * @param extensions A list of default extensions to show in the file chooser. + * For example, String[] { "txt", "java" }. These might be ignored by the JNLP Client. + * @param stream The content of the file to save along represented as an InputStream + * @param name The suggested filename, which might be ignored by the JNLP client + * @return A FileContents object for the saved file if the save was successfully, + * or null if the user canceled the request. + * @throws IOException if the requested failed in any way other than the user chose not to save the file + */ + FileContents saveFileDialog(String pathHint, String[] extensions, InputStream stream, String name) throws IOException; - public FileContents saveAsFileDialog(java.lang.String pathHint, java.lang.String[] extensions, FileContents contents) throws java.io.IOException; + /** + * Asks the users to save a file. + * + * @param pathHint A hint from the application to the default directory to be used. + * This might be ignored by the JNLP Client. + * @param extensions A list of default extensions to show in the file chooser. + * For example, String[] { "txt", "java" }. These might be ignored by the JNLP Client. + * @param contents The content of the file to save along with the suggested filename. + * The suggested filename might be ignored by the JNLP Client. + * @return A FileContents object for the saved file if the save was successfully, + * or null if the user canceled the request. + * @throws IOException if the requested failed in any way other than the user chose not to save the file + */ + FileContents saveAsFileDialog(String pathHint, String[] extensions, FileContents contents) throws IOException; } diff --git a/launchers/configure.sh b/launchers/configure.sh index a188d0097..fe6f276b6 100644 --- a/launchers/configure.sh +++ b/launchers/configure.sh @@ -118,6 +118,11 @@ if [ "x$MSLINKS_SRC" == "x" ] ; then else readonly MSLINKS_SRC=$MSLINKS_SRC fi +if [ "x$GSON_SRC" == "x" ] ; then + readonly GSON_SRC=`getJar "gson"` +else + readonly GSON_SRC=$GSON_SRC +fi if [ "x$IPADDRESS_SRC" == "x" ] ; then readonly IPADDRESS_SRC=`getJar "ipaddress"` else diff --git a/launchers/pom.xml b/launchers/pom.xml index a81393608..2b5cebc98 100644 --- a/launchers/pom.xml +++ b/launchers/pom.xml @@ -1,84 +1,84 @@ - - - 4.0.0 - - - net.adoptopenjdk - icedtea-web-parent - 2.0.0-SNAPSHOT - ../ - - - icedtea-web-launchers - Launchers - Set of default native and shell launchers for ITW - pom - - - - - ${project.groupId} - jnlp-api - ${project.version} - - - ${project.groupId} - icedtea-web-common - ${project.version} - - - ${project.groupId} - icedtea-web-xml-parser - ${project.version} - - - ${project.groupId} - icedtea-web-clients - ${project.version} - - - - - - launchers - - - - exec-maven-plugin - org.codehaus.mojo - 1.6.0 - - - build-launchers - install - - exec - - - bash - ${basedir}/build.sh ${project.version} - ${basedir}/build.log - - - - clean-launchers - clean - - exec - - - rm - -rvf ${basedir}/target ${basedir}/rust-launcher/target/ - ${basedir}/build.log - - - - - - - - - - + + + 4.0.0 + + + net.adoptopenjdk + icedtea-web-parent + 3.0.0-SNAPSHOT + ../ + + + icedtea-web-launchers + Launchers + Set of default native and shell launchers for ITW + pom + + + + + ${project.groupId} + jnlp-api + ${project.version} + + + ${project.groupId} + icedtea-web-common + ${project.version} + + + ${project.groupId} + icedtea-web-xml-parser + ${project.version} + + + ${project.groupId} + icedtea-web-clients + ${project.version} + + + + + + launchers + + + + exec-maven-plugin + org.codehaus.mojo + 1.6.0 + + + build-launchers + install + + exec + + + bash + ${basedir}/build.sh ${project.version} + ${basedir}/build.log + + + + clean-launchers + clean + + exec + + + rm + -rvf ${basedir}/target ${basedir}/rust-launcher/target/ + ${basedir}/build.log + + + + + + + + + + diff --git a/launchers/rust-launcher/src/hardcoded_paths.rs b/launchers/rust-launcher/src/hardcoded_paths.rs index adffdf206..05a72d09c 100644 --- a/launchers/rust-launcher/src/hardcoded_paths.rs +++ b/launchers/rust-launcher/src/hardcoded_paths.rs @@ -14,6 +14,7 @@ const RHINO_JAR: Option<&'static str> = option_env!("RHINO_JAR"); const ITW_LIBS: Option<&'static str> = option_env!("ITW_LIBS"); const MODULARJDK_ARGS_LOCATION: Option<&'static str> = option_env!("MODULARJDK_ARGS_LOCATION"); const MSLINKS_JAR: Option<&'static str> = option_env!("MSLINKS_JAR"); +const GSON_JAR: Option<&'static str> = option_env!("GSON_JAR"); const IPADDRESS_JAR: Option<&'static str> = option_env!("IPADDRESS_JAR"); @@ -39,6 +40,8 @@ pub fn get_rhino() -> Option<&'static str> { sanitize(RHINO_JAR) } pub fn get_mslinks() -> Option<&'static str> { sanitize(MSLINKS_JAR) } +pub fn get_gson() -> Option<&'static str> { sanitize(GSON_JAR) } + pub fn get_ipaddress() -> Option<&'static str> { sanitize(IPADDRESS_JAR) } pub fn get_argsfile() -> &'static str { diff --git a/launchers/rust-launcher/src/jars_helper.rs b/launchers/rust-launcher/src/jars_helper.rs index 8de8c5eb6..40feeab0e 100644 --- a/launchers/rust-launcher/src/jars_helper.rs +++ b/launchers/rust-launcher/src/jars_helper.rs @@ -153,6 +153,7 @@ fn get_bootcp_members(jre_path: &std::path::PathBuf, os: &os_access::Os) -> Vec< append_if_exists(hardcoded_paths::get_rhino(), os, &mut cp_parts); append_if_exists(hardcoded_paths::get_tagsoup(), os, &mut cp_parts); append_if_exists(hardcoded_paths::get_mslinks(), os, &mut cp_parts); + append_if_exists(hardcoded_paths::get_gson(), os, &mut cp_parts); append_if_exists(hardcoded_paths::get_ipaddress(), os, &mut cp_parts); let mut nashorn_jar = jre_path.clone(); nashorn_jar.push("lib"); diff --git a/launchers/shell-launcher/launchers.bat.in b/launchers/shell-launcher/launchers.bat.in index 32ae08cb9..56152da8d 100644 --- a/launchers/shell-launcher/launchers.bat.in +++ b/launchers/shell-launcher/launchers.bat.in @@ -29,6 +29,7 @@ set "JAVAWS_SRC=@JAVAWS_JAR@" set "TAGSOUP_SRC=@TAGSOUP_JAR@" set "RHINO_SRC=@RHINO_JAR@" set "MSLINKS_SRC=@MSLINKS_JAR@" +set "GSON_SRC=@GSON_JAR@" set "IPADDRESS_SRC=@IPADDRESS_JAR@" @@ -107,6 +108,7 @@ if "%ITW_LIBS%" == "BUNDLED" ( set "RHINO_JAR=" set "TAGSOUP_JAR=" set "MSLINKS_JAR=" + set "GSON_JAR=" set "IPADDRESS_JAR=" ) else ( set "JAVAWS_JAR=!JAVAWS_SRC!" @@ -114,9 +116,10 @@ if "%ITW_LIBS%" == "BUNDLED" ( set "RHINO_JAR=!RHINO_SRC!" set "TAGSOUP_JAR=!TAGSOUP_SRC!" set "MSLINKS_JAR=!MSLINKS_SRC!" + set "GSON_JAR=!GSON_SRC!" set "IPADDRESS_JAR=!IPADDRESS_SRC!" ) -set "LAUNCHER_BOOTCLASSPATH=-Xbootclasspath/a:%JAVAWS_JAR%;%RHINO_JAR%;%TAGSOUP_JAR%;%MSLINKS_JAR%;%IPADDRESS_JAR%" +set "LAUNCHER_BOOTCLASSPATH=-Xbootclasspath/a:%JAVAWS_JAR%;%RHINO_JAR%;%TAGSOUP_JAR%;%MSLINKS_JAR%;%GSON_JAR%;%IPADDRESS_JAR%;" rem Fix classpaths for custom JRE: diff --git a/launchers/shell-launcher/launchers.sh.in b/launchers/shell-launcher/launchers.sh.in index e27988f4f..6f88d7005 100644 --- a/launchers/shell-launcher/launchers.sh.in +++ b/launchers/shell-launcher/launchers.sh.in @@ -20,6 +20,7 @@ ITW_LIBS=@ITW_LIBS@ readonly JAVAWS_SRC=@JAVAWS_JAR@ readonly TAGSOUP_SRC=@TAGSOUP_JAR@ readonly RHINO_SRC=@RHINO_JAR@ +readonly GSON_SRC=@GSON_JAR@ readonly IPADDRESS_SRC=@IPADDRESS_JAR@ # windows only: (cywin usage?); need to declare it anyway readonly MSLINKS_SRC=@MSLINKS_JAR@ @@ -109,6 +110,7 @@ if [ "x$ITW_LIBS" == "xBUNDLED" -o ! "x$ITW_HOME" = "x" ] ; then readonly RHINO_JAR="" readonly TAGSOUP_JAR="" readonly MSLINKS_JAR="" + readonly GSON_JAR="" readonly IPADDRESS_JAR="" echo "warning, using portable itw from $ITW_HOME" else @@ -117,10 +119,11 @@ else readonly RHINO_JAR="$RHINO_SRC" readonly TAGSOUP_JAR="$TAGSOUP_SRC" readonly MSLINKS_JAR="$MSLINKS_SRC" + readonly GSON_JAR="$GSON_SRC" readonly IPADDRESS_JAR="$IPADDRESS_SRC" fi set -u -LAUNCHER_BOOTCLASSPATH="-Xbootclasspath/a:$JAVAWS_JAR:$TAGSOUP_JAR:$RHINO_JAR:$MSLINKS_JAR:$IPADDRESS_JAR" +LAUNCHER_BOOTCLASSPATH="-Xbootclasspath/a:$JAVAWS_JAR:$TAGSOUP_JAR:$RHINO_JAR:$MSLINKS_JAR:$GSON_J:$IPADDRESS_JARAR" # Fix classpaths for custom JRE: if [ "x$CUSTOM_JRE" != "x" ] ; then diff --git a/launchers/utils.sh b/launchers/utils.sh index ef773997b..3ad0d007b 100644 --- a/launchers/utils.sh +++ b/launchers/utils.sh @@ -22,6 +22,7 @@ function build() { export TAGSOUP_JAR=$TAGSOUP_SRC export RHINO_JAR=$RHINO_SRC export MSLINKS_JAR=$MSLINKS_SRC + export GSON_JAR=$GSON_SRC export IPADDRESS_JAR=$IPADDRESS_SRC fi export JRE @@ -54,6 +55,7 @@ function build() { -e "s|[@]TAGSOUP_JAR[@]|$TAGSOUP_JAR|g" \ -e "s|[@]RHINO_JAR[@]|$RHINO_JAR|g" \ -e "s|[@]MSLINKS_JAR[@]|$MSLINKS_JAR|g" \ + -e "s|[@]JSON_JAR[@]|$JSON_JAR|g" \ -e "s|[@]IPADDRESS_JAR[@]|$IPADDRESS_JAR|g" \ -e "s|[@]JAVAWS_JAR[@]|$JAVAWS_JAR|g" \ -e "s|[@]SPLASH_PNG[@]|$SPLASH_PNG|g" \ @@ -182,9 +184,9 @@ function sedDesktopIcons() { function cutIfNecessary() { #for distribution, we uses only the "installed" part of path - if [ $ITW_LIBS == "DISTRIBUTION" ] ; then + if [ $ITW_LIBS == "DISTRIBUTION" ] ; then echo "$1" | sed "s|$TARGET||g" - else + else echo "$1" fi } diff --git a/pom.xml b/pom.xml index db812e2e4..27af5a4ab 100644 --- a/pom.xml +++ b/pom.xml @@ -1,179 +1,181 @@ - - - 4.0.0 - - net.adoptopenjdk - icedtea-web-parent - 2.0.0-SNAPSHOT - pom - - IcedTeaWeb - Multi-module for IcedTea-Web. - - - https://github.com/AdoptOpenJDK/IcedTea-Web - scm:git:https://git@github.com:AdoptOpenJDK/IcedTea-Web.git - scm:git:https://git@github.com:AdoptOpenJDK/IcedTea-Web.git - HEAD - - - - 1.8 - 1.8 - UTF-8 - - 3.1.2 - 3.1.0 - 3.0.0-M3 - 0.8.4 - 3.1.0 - 3.1.0 - - true - - - - - javaDoc - - false - - - - - - jnlp-api - common - test-extensions - xml-parser - core - artifact-no-dependencies - artifact-all-dependencies - integration - clients - launchers - - - - - jcenter - https://jcenter.bintray.com/ - - - - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.4 - - - - create - - - - - 8 - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar - - - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin.version} - - - - true - true - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - -Dfile.encoding=${project.build.sourceEncoding} -Duser.language=en - - DEFAULT - - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - package - - jar - - - ${skip.javadoc} - none - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - - - - + + + 4.0.0 + + net.adoptopenjdk + icedtea-web-parent + 3.0.0-SNAPSHOT + pom + + IcedTeaWeb + Multi-module for IcedTea-Web. + + + https://github.com/AdoptOpenJDK/IcedTea-Web + scm:git:https://git@github.com:AdoptOpenJDK/IcedTea-Web.git + scm:git:https://git@github.com:AdoptOpenJDK/IcedTea-Web.git + HEAD + + + + 1.8 + 1.8 + UTF-8 + + 3.1.2 + 3.1.0 + 3.0.0-M3 + 0.8.4 + 3.1.0 + 3.1.0 + + true + + + + + javaDoc + + false + + + + + + jnlp-api + common + test-extensions + xml-parser + core + artifact-no-dependencies + artifact-all-dependencies + + clients + launchers + + integration-tests + + + + + jcenter + https://jcenter.bintray.com/ + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + + create + + + + + 8 + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + true + true + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + -Dfile.encoding=${project.build.sourceEncoding} -Duser.language=en + + DEFAULT + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + package + + jar + + + ${skip.javadoc} + none + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + + + + diff --git a/test-extensions/pom.xml b/test-extensions/pom.xml index dfd9111ea..978ddea96 100644 --- a/test-extensions/pom.xml +++ b/test-extensions/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ diff --git a/xml-parser/pom.xml b/xml-parser/pom.xml index d33cbf259..e656ab695 100644 --- a/xml-parser/pom.xml +++ b/xml-parser/pom.xml @@ -7,7 +7,7 @@ net.adoptopenjdk icedtea-web-parent - 2.0.0-SNAPSHOT + 3.0.0-SNAPSHOT ../