Mercurial > hg > release > icedtea-web-1.5
changeset 935:aba4c18c4c64
Base implementation of Application-Library-Allowable-Codebase. Remember button not yet working.
author | Jiri Vanek <jvanek@redhat.com> |
---|---|
date | Fri, 14 Mar 2014 13:06:03 +0100 |
parents | dfc27d4d55d5 |
children | fdff61a60cc1 |
files | ChangeLog netx/net/sourceforge/jnlp/JNLPFile.java netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/security/SecurityDialog.java netx/net/sourceforge/jnlp/security/SecurityDialogs.java netx/net/sourceforge/jnlp/security/dialogs/MatchingALACAttributePanel.java netx/net/sourceforge/jnlp/security/dialogs/MissingALACAttributePanel.java netx/net/sourceforge/jnlp/util/ClasspathMatcher.java netx/net/sourceforge/jnlp/util/UrlUtils.java tests/netx/unit/net/sourceforge/jnlp/util/ClasspathMatcherTest.java tests/netx/unit/net/sourceforge/jnlp/util/UrlUtilsTest.java |
diffstat | 12 files changed, 846 insertions(+), 20 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Thu Mar 13 13:48:00 2014 -0400 +++ b/ChangeLog Fri Mar 14 13:06:03 2014 +0100 @@ -1,3 +1,36 @@ +2014-03-14 Jiri Vanek <jvanek@redhat.com> + + Base implementation of Application-Library-Allowable-Codebase. Remember + button not yet working. + * netx/net/sourceforge/jnlp/JNLPFile.java: (ClasspathMatchers) + (getApplicationLibraryAllowableCodebase) (getCodebase) (getCodeBaseMatchersAttribute) + (getCodeBaseMatchersAttribute) (getCodeBaseMatchersAttribute) changed signature + to include/not include path in returned matcher. + * netx/net/sourceforge/jnlp/resources/Messages.properties: Added keys + (ALACAMissingMainTitle) (ALACAMissingInfo) (ALACAMatchingMainTitle) + (ALACAMatchingInfo) for new dialogs. + * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: Implemented + (checkApplicationLibraryAllowableCodebaseAttribute). Used in (init) + * netx/net/sourceforge/jnlp/security/SecurityDialog.java: made aware of + new constants (MISSING_ALACA) and (MATCHING_ALACA) + * netx/net/sourceforge/jnlp/security/SecurityDialogs.java: new constants + (MISSING_ALACA) and (MATCHING_ALACA). Implemented (showMissingALACAttributePanel) + and (showMatchingALACAttributePanel) + * netx/net/sourceforge/jnlp/security/dialogs/MatchingALACAttributePanel.java + new dialog for Matching attribute + * netx/net/sourceforge/jnlp/security/dialogs/MissingALACAttributePanel.java: + new dialog for Missing attribute. + * netx/net/sourceforge/jnlp/util/ClasspathMatcher.java: allowing user to + choose whether to include paths in matching or not. + * netx/net/sourceforge/jnlp/util/UrlUtils.java: new util methods (removeFileName) + (setOfUrlsToHtmlList) (sanitizeLastSlash) and (equalsIgnoreLastSlash) to + strip filename from url, toString for iterable of urls to string, and + for operations with URLs independently on last slash + * tests/netx/unit/net/sourceforge/jnlp/util/ClasspathMatcherTest.java: added + tests for paths + * tests/netx/unit/net/sourceforge/jnlp/util/UrlUtilsTest.java: added tests + for new methods + 2014-03-13 Andrew Azores <aazores@redhat.com> * netx/net/sourceforge/jnlp/security/policyeditor/PolicyEditor.java:
--- a/netx/net/sourceforge/jnlp/JNLPFile.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/JNLPFile.java Fri Mar 14 13:06:03 2014 +0100 @@ -920,21 +920,21 @@ * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/manifest.html#caller_allowable */ public ClasspathMatcher.ClasspathMatchers getCallerAllowableCodebase() { - return getCodeBaseMatchersAttribute(CALLER_ALLOWABLE); + return getCodeBaseMatchersAttribute(CALLER_ALLOWABLE, false); } /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/manifest.html#app_library */ public ClasspathMatcher.ClasspathMatchers getApplicationLibraryAllowableCodebase() { - return getCodeBaseMatchersAttribute(APP_LIBRARY_ALLOWABLE); + return getCodeBaseMatchersAttribute(APP_LIBRARY_ALLOWABLE, true); } /** * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/manifest.html#codebase */ public ClasspathMatcher.ClasspathMatchers getCodebase() { - return getCodeBaseMatchersAttribute(CODEBASE); + return getCodeBaseMatchersAttribute(CODEBASE, false); } /** @@ -1004,16 +1004,16 @@ return loader.checkForAttributeInJars(Arrays.asList(getResources().getJARs()), name); } - public ClasspathMatcher.ClasspathMatchers getCodeBaseMatchersAttribute(String s) { - return getCodeBaseMatchersAttribute(new Attributes.Name(s)); + public ClasspathMatcher.ClasspathMatchers getCodeBaseMatchersAttribute(String s, boolean includePath) { + return getCodeBaseMatchersAttribute(new Attributes.Name(s), includePath); } - public ClasspathMatcher.ClasspathMatchers getCodeBaseMatchersAttribute(Attributes.Name name) { + public ClasspathMatcher.ClasspathMatchers getCodeBaseMatchersAttribute(Attributes.Name name, boolean includePath) { String s = getAttribute(name); if (s == null) { return null; } - return ClasspathMatcher.ClasspathMatchers.compile(s); + return ClasspathMatcher.ClasspathMatchers.compile(s, includePath); } private ManifestBoolean processBooleanAttribute(String id) throws IllegalArgumentException {
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties Fri Mar 14 13:06:03 2014 +0100 @@ -58,6 +58,25 @@ and<br/> <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html"> \ http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html</a> +# missing Application-Library-Allowable-Codebase dialogue +ALACAMissingMainTitle=Application <span color='red'> {0} </span> \ +form codebase <span color='red'> {1} </span> is missing the Application-Library-Allowable-Codebase attribute. \ +This application uses resources from the following remote locations:<br/> {2} Are you sure you want to run this application? +ALACAMissingInfo=For more information you can visit:<br/>\ +<a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library"> \ +http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library</a> <br/> \ +and<br/> <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html"> \ +http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html</a> +# matching Application-Library-Allowable-Codebase dialogue +ALACAMatchingMainTitle=Application <span color='red'> {0} </span> \ +form codebase <span color='red'> {1} </span> is requiring valid resources from different locations:<br/>{2} <br/> \ +Those resources are expected to be loaded. Do you agree to run this application? +ALACAMatchingInfo=For more information you can visit:<br/>\ +<a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library"> \ +http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library</a> <br/> \ +and<br/> <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html"> \ +http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/no_redeploy.html</a> + # LS - Severity LSMinor=Minor LSFatal=Fatal
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Fri Mar 14 13:06:03 2014 +0100 @@ -92,6 +92,7 @@ import net.sourceforge.jnlp.util.ClasspathMatcher.ClasspathMatchers; import net.sourceforge.jnlp.util.JarFile; import net.sourceforge.jnlp.util.StreamUtils; +import net.sourceforge.jnlp.util.UrlUtils; import net.sourceforge.jnlp.util.logging.OutputController; import sun.misc.JarIndex; @@ -292,6 +293,8 @@ checkPermissionsAttribute(); + checkApplicationLibraryAllowableCodebaseAttribute(); + installShutdownHooks(); @@ -2324,6 +2327,96 @@ public boolean getRunInSandbox(); } + private void checkApplicationLibraryAllowableCodebaseAttribute() throws LaunchException { + if (signing == SigningState.NONE){ + return; /*when app is not signed at all, then skip this check*/ + } + //conditions + URL codebase = file.getCodeBase(); + URL documentBase = null; + if (file instanceof PluginBridge) { + documentBase = ((PluginBridge) file).getSourceLocation(); + } + if (documentBase == null) { + documentBase = file.getCodeBase(); + } + + //cases + Set<URL> usedUrls = new HashSet<URL>(); + URL sourceLocation = file.getSourceLocation(); + ResourcesDesc[] resourcesDescs = file.getResourcesDescs(); + if (sourceLocation != null) { + usedUrls.add(UrlUtils.removeFileName(sourceLocation)); + } + for (ResourcesDesc resourcesDesc: resourcesDescs) { + ExtensionDesc[] ex = resourcesDesc.getExtensions(); + if (ex != null) { + for ( ExtensionDesc extensionDesc: ex) { + if (extensionDesc != null) { + usedUrls.add(UrlUtils.removeFileName(extensionDesc.getLocation())); + } + } + } + JARDesc[] jars = resourcesDesc.getJARs(); + if (jars != null) { + for (JARDesc jarDesc: jars) { + if (jarDesc != null) { + usedUrls.add(UrlUtils.removeFileName(jarDesc.getLocation())); + } + } + } + JNLPFile jnlp = resourcesDesc.getJNLPFile(); + if (jnlp != null) { + usedUrls.add(UrlUtils.removeFileName(jnlp.getSourceLocation())); + } + + } + OutputController.getLogger().log("Found alaca URLs to be verified"); + for (URL url : usedUrls) { + OutputController.getLogger().log(" - " + url.toExternalForm()); + } + if (usedUrls.isEmpty()) { + //I hope this is the case, when the resources is/are + //only codebase classes. Then it should be safe to return. + OutputController.getLogger().log("The application is not using any url resources, skipping Application-Library-Allowable-Codebase Attribute check."); + return; + } + + if (usedUrls.size() == 1) { + if (UrlUtils.equalsIgnoreLastSlash(usedUrls.toArray(new URL[0])[0], codebase) + && UrlUtils.equalsIgnoreLastSlash(usedUrls.toArray(new URL[0])[0], documentBase)) { + //all resoources are from codebase or document base. it is ok to proceeed. + OutputController.getLogger().log("All applications resources (" + usedUrls.toArray(new URL[0])[0] + ") are from codebas/documentbase " + codebase + "/" + documentBase + ", skipping Application-Library-Allowable-Codebase Attribute check."); + return; + } + } + ClasspathMatchers att = file.getManifestsAttributes().getApplicationLibraryAllowableCodebase(); + + if (att == null) { + boolean a = SecurityDialogs.showMissingALACAttributePanel(file.getTitle(), documentBase, usedUrls); + if (!a) { + 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 { + OutputController.getLogger().log("The application uses non-codebase resources, has no Application-Library-Allowable-Codebase Attribute, and was allowed to run by the user"); + return; + } + } else { + for (URL foundUrl : usedUrls) { + if (!att.matches(foundUrl)) { + throw new LaunchException("The resource from " + foundUrl + " does not match the location in Application-Library-Allowable-Codebase Attribute " + att + ". Blocking the application from running."); + } else { + OutputController.getLogger().log("The resource from " + foundUrl + " does match the location in Application-Library-Allowable-Codebase Attribute " + att + ". Continuing."); + } + } + } + boolean a = SecurityDialogs.showMatchingALACAttributePanel(file.getTitle(), documentBase, usedUrls); + if (!a) { + 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 { + OutputController.getLogger().log("The application uses non-codebase resources, which do match its Application-Library-Allowable-Codebase Attribute, and was allowed to run by the user." ); + } + } + /** * Handles security decision logic for the JNLPClassLoader, eg which permission level to assign * to JARs.
--- a/netx/net/sourceforge/jnlp/security/SecurityDialog.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/security/SecurityDialog.java Fri Mar 14 13:06:03 2014 +0100 @@ -37,6 +37,8 @@ package net.sourceforge.jnlp.security; +import net.sourceforge.jnlp.security.dialogs.MissingALACAttributePanel; +import net.sourceforge.jnlp.security.dialogs.MatchingALACAttributePanel; import net.sourceforge.jnlp.security.dialogs.MissingPermissionsAttributePanel; import net.sourceforge.jnlp.security.dialogs.AppletWarningPane; import net.sourceforge.jnlp.security.dialogs.AccessWarningPane; @@ -320,6 +322,10 @@ panel = new PasswordAuthenticationPane(this, extras); else if (dialogType == DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING) panel = new MissingPermissionsAttributePanel(this, (String) extras[0], (String) extras[1]); + else if (dialogType == DialogType.MISSING_ALACA) + panel = new MissingALACAttributePanel(this, (String) extras[0], (String) extras[1], (String) extras[2]); + else if (dialogType == DialogType.MATCHING_ALACA) + panel = new MatchingALACAttributePanel(this, (String) extras[0], (String) extras[1], (String) extras[2]); add(panel, BorderLayout.CENTER); }
--- a/netx/net/sourceforge/jnlp/security/SecurityDialogs.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/security/SecurityDialogs.java Fri Mar 14 13:06:03 2014 +0100 @@ -44,6 +44,7 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Set; import java.util.concurrent.Semaphore; import javax.swing.JDialog; @@ -52,6 +53,7 @@ import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.config.DeploymentConfiguration; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.UrlUtils; import net.sourceforge.jnlp.security.dialogs.apptrustwarningpanel.AppTrustWarningPanel.AppSigningWarningAction; import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteAppletAction; @@ -80,7 +82,9 @@ 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, */ + 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 } /** The types of access which may need user permission. */ @@ -263,6 +267,32 @@ return (Object[]) response; } + public static boolean showMissingALACAttributePanel(String title, URL codeBase, Set<URL> remoteUrls) { + + if (!shouldPromptUser()) { + return false; + } + + SecurityDialogMessage message = new SecurityDialogMessage(); + message.dialogType = DialogType.MISSING_ALACA; + message.extras = new Object[]{title, codeBase.toString(), UrlUtils.setOfUrlsToHtmlList(remoteUrls)}; + Object selectedValue = getUserResponse(message); + return getIntegerResponseAsBoolean(selectedValue); + } + + public static boolean showMatchingALACAttributePanel(String title, URL codeBase, Set<URL> remoteUrls) { + + if (!shouldPromptUser()) { + return false; + } + + SecurityDialogMessage message = new SecurityDialogMessage(); + message.dialogType = DialogType.MATCHING_ALACA; + message.extras = new Object[]{title, codeBase.toString(), UrlUtils.setOfUrlsToHtmlList(remoteUrls)}; + Object selectedValue = getUserResponse(message); + return getIntegerResponseAsBoolean(selectedValue); + } + /** * FIXME This is unused. Remove it? * @return (0, 1, 2) => (Yes, No, Cancel) @@ -417,5 +447,5 @@ } }); } - + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/netx/net/sourceforge/jnlp/security/dialogs/MatchingALACAttributePanel.java Fri Mar 14 13:06:03 2014 +0100 @@ -0,0 +1,165 @@ +/* + Copyright (C) 2008 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.dialogs; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.FlowLayout; +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 javax.imageio.ImageIO; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import net.sourceforge.jnlp.runtime.Translator; +import net.sourceforge.jnlp.security.SecurityDialog; +import net.sourceforge.jnlp.util.UrlUtils; +import net.sourceforge.jnlp.util.logging.OutputController; + +/** + * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library + */ +public class MatchingALACAttributePanel extends SecurityDialogPanel { + + public MatchingALACAttributePanel(SecurityDialog x, String title, String codebase, String remoteUrls) { + super(x); + try { + addComponents(title, codebase, remoteUrls); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + if (x != null) { + x.setMinimumSize(new Dimension(600, 400)); + } + } + + protected final void addComponents(String title, String codebase, String remoteUrls) throws IOException { + + URL imgUrl = this.getClass().getResource("/net/sourceforge/jnlp/resources/question.png"); + ImageIcon icon; + Image img = ImageIO.read(imgUrl); + icon = new ImageIcon(img); + String topLabelText = Translator.R("ALACAMatchingMainTitle", title, codebase, remoteUrls); + String bottomLabelText = Translator.R("ALACAMatchingInfo"); + + JLabel topLabel = new JLabel(htmlWrap(topLabelText), icon, SwingConstants.CENTER); + topLabel.setFont(new Font(topLabel.getFont().toString(), + Font.BOLD, 12)); + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.setBackground(Color.WHITE); + topPanel.add(topLabel, BorderLayout.CENTER); + topPanel.setPreferredSize(new Dimension(400, 80)); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JEditorPane bottomLabel = new JEditorPane("text/html", htmlWrap(bottomLabelText)); + bottomLabel.setEditable(false); + bottomLabel.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + try { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + Desktop.getDesktop().browse(e.getURL().toURI()); + } + } catch (IOException ex) { + OutputController.getLogger().log(ex); + } catch (URISyntaxException ex) { + OutputController.getLogger().log(ex); + } + } + }); + JPanel infoPanel = new JPanel(new BorderLayout()); + infoPanel.add(bottomLabel, BorderLayout.CENTER); + infoPanel.setPreferredSize(new Dimension(400, 80)); + infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + bottomLabel.setBackground(infoPanel.getBackground()); + + //run and cancel buttons + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + JButton yes = new JButton(Translator.R("ButYes")); + JButton no = new JButton(Translator.R("ButNo")); + JCheckBox remeber = new JCheckBox(htmlWrap(Translator.R("SRememberOption"))); + int buttonWidth = yes.getMinimumSize().width; + int buttonHeight = yes.getMinimumSize().height; + Dimension d = new Dimension(buttonWidth, buttonHeight); + yes.setPreferredSize(d); + no.setPreferredSize(d); + yes.addActionListener(createSetValueListener(parent, 0)); + no.addActionListener(createSetValueListener(parent, 1)); + initialFocusComponent = no; + buttonPanel.add(remeber); + buttonPanel.add(yes); + buttonPanel.add(no); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + //all of the above + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(topPanel); + add(infoPanel); + add(buttonPanel); + + } + + public static void main(String[] args) throws MalformedURLException { + Set<URL> s = new HashSet<URL>(); + s.add(new URL("http:/blah.com/blah")); + s.add(new URL("http:/blah.com/blah/blah")); + MatchingALACAttributePanel w = new MatchingALACAttributePanel(null, "HelloWorld", "http://nbblah.url", UrlUtils.setOfUrlsToHtmlList(s)); + JFrame f = new JFrame(); + f.setSize(600, 400); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.add(w, BorderLayout.CENTER); + f.setVisible(true); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/netx/net/sourceforge/jnlp/security/dialogs/MissingALACAttributePanel.java Fri Mar 14 13:06:03 2014 +0100 @@ -0,0 +1,162 @@ +/* + Copyright (C) 2008 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.dialogs; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.FlowLayout; +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 javax.imageio.ImageIO; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +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; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import net.sourceforge.jnlp.runtime.Translator; +import net.sourceforge.jnlp.security.SecurityDialog; +import net.sourceforge.jnlp.util.UrlUtils; +import net.sourceforge.jnlp.util.logging.OutputController; + +/** + * http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html#app_library + */ +public class MissingALACAttributePanel extends SecurityDialogPanel { + + public MissingALACAttributePanel(SecurityDialog x, String title, String codebase, String remoteUrls) { + super(x); + try { + addComponents(title, codebase, remoteUrls); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + if (x != null) { + x.setMinimumSize(new Dimension(600, 400)); + } + } + + protected final void addComponents(String title, String codebase, String remoteUrls) throws IOException { + + URL imgUrl = this.getClass().getResource("/net/sourceforge/jnlp/resources/warning.png"); + ImageIcon icon; + Image img = ImageIO.read(imgUrl); + icon = new ImageIcon(img); + String topLabelText = Translator.R("ALACAMissingMainTitle", title, codebase, remoteUrls); + String bottomLabelText = Translator.R("ALACAMissingInfo"); + + JLabel topLabel = new JLabel(htmlWrap(topLabelText), icon, SwingConstants.CENTER); + topLabel.setFont(new Font(topLabel.getFont().toString(), + Font.BOLD, 12)); + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.setBackground(Color.WHITE); + topPanel.add(topLabel, BorderLayout.CENTER); + topPanel.setPreferredSize(new Dimension(400, 80)); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JEditorPane bottomLabel = new JEditorPane("text/html", htmlWrap(bottomLabelText)); + bottomLabel.setEditable(false); + bottomLabel.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + try { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + Desktop.getDesktop().browse(e.getURL().toURI()); + } + } catch (IOException ex) { + OutputController.getLogger().log(ex); + } catch (URISyntaxException ex) { + OutputController.getLogger().log(ex); + } + } + }); + JPanel infoPanel = new JPanel(new BorderLayout()); + infoPanel.add(bottomLabel, BorderLayout.CENTER); + infoPanel.setPreferredSize(new Dimension(400, 80)); + infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + bottomLabel.setBackground(infoPanel.getBackground()); + + //run and cancel buttons + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + JButton yes = new JButton(Translator.R("ButYes")); + JButton no = new JButton(Translator.R("ButNo")); + int buttonWidth = yes.getMinimumSize().width; + int buttonHeight = yes.getMinimumSize().height; + Dimension d = new Dimension(buttonWidth, buttonHeight); + yes.setPreferredSize(d); + no.setPreferredSize(d); + yes.addActionListener(createSetValueListener(parent, 0)); + no.addActionListener(createSetValueListener(parent, 1)); + initialFocusComponent = no; + buttonPanel.add(yes); + buttonPanel.add(no); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + //all of the above + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + add(topPanel); + add(infoPanel); + add(buttonPanel); + + } + + public static void main(String[] args) throws MalformedURLException { + Set<URL> s = new HashSet<URL>(); + 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); + } +}
--- a/netx/net/sourceforge/jnlp/util/ClasspathMatcher.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/util/ClasspathMatcher.java Fri Mar 14 13:06:03 2014 +0100 @@ -24,6 +24,7 @@ public static class ClasspathMatchers { private final ArrayList<ClasspathMatcher> matchers; + private final boolean includePath; ArrayList<ClasspathMatcher> getMatchers() { return matchers; @@ -36,8 +37,12 @@ * @return */ public static ClasspathMatchers compile(String s) { + return compile(s, false); + } + + public static ClasspathMatchers compile(String s, boolean includePath) { if (s == null) { - return new ClasspathMatchers(new ArrayList<ClasspathMatcher>(0)); + return new ClasspathMatchers(new ArrayList<ClasspathMatcher>(0), includePath); } String[] splitted = s.trim().split("\\s+"); ArrayList<ClasspathMatcher> matchers = new ArrayList<ClasspathMatcher>(splitted.length); @@ -45,11 +50,12 @@ matchers.add(ClasspathMatcher.compile(string.trim())); } - return new ClasspathMatchers(matchers); + return new ClasspathMatchers(matchers, includePath); } - public ClasspathMatchers(ArrayList<ClasspathMatcher> matchers) { + public ClasspathMatchers(ArrayList<ClasspathMatcher> matchers, boolean includePath) { this.matchers = matchers; + this.includePath = includePath; } public boolean matches(URL s) { @@ -58,7 +64,7 @@ private boolean or(URL s) { for (ClasspathMatcher classpathMatcher : matchers) { - if (classpathMatcher.match(s)) { + if (classpathMatcher.match(s, includePath)) { return true; } } @@ -67,7 +73,7 @@ private boolean and(URL s) { for (ClasspathMatcher classpathMatcher : matchers) { - if (!classpathMatcher.match(s)) { + if (!classpathMatcher.match(s, includePath)) { return false; } } @@ -171,11 +177,7 @@ return r; } - public boolean match(URL url) { - //path is not counted in specification - return matchWithoutPath(url); - } - + private boolean match(URL url, boolean includePath) { String protocol = url.getProtocol(); int port = url.getPort(); //negative if not set @@ -186,7 +188,9 @@ && parts.matchDomain(domain); if (includePath) { - return always && parts.matchPath(path); + return always + && (parts.matchPath(UrlUtils.sanitizeLastSlash(path)) + || parts.matchPath(path)); } else { return always; } @@ -195,6 +199,10 @@ /* * For testing purposes */ + public boolean match(URL url) { + return match(url, false); + } + public boolean matchWithPath(URL url) { return match(url, true); }
--- a/netx/net/sourceforge/jnlp/util/UrlUtils.java Thu Mar 13 13:48:00 2014 -0400 +++ b/netx/net/sourceforge/jnlp/util/UrlUtils.java Fri Mar 14 13:06:03 2014 +0100 @@ -153,4 +153,109 @@ public static File decodeUrlAsFile(URL url) { return new File(decodeUrlQuietly(url).getFile()); } + + /** + * This function i striping part behind last path delimiter. + * + * Expected is input like protcol://som.url/some/path/file.suff + * Then output will bee protcol://som.url/some/path + * + * Be aware of input like protcol://som.url/some/path/ + * then input will be just protcol://som.url/some/path + * + * You can use sanitizeLastSlash and see also unittests + * Both unix and windows salshes are supported + * + * @param src + * @return + */ + public static URL removeFileName(final URL src) { + URL nsrc = normalizeUrlAndStripParams(src); + String s = nsrc.getPath(); + int i1 = s.lastIndexOf("/"); + int i2 = s.lastIndexOf("\\"); + int i = Math.max(i1, i2); + if (i < 0) { + return src; + } + s = s.substring(0, i); + try { + return sanitizeLastSlash(new URL(src.getProtocol(), src.getHost(), src.getPort(), s)); + } catch (MalformedURLException ex) { + OutputController.getLogger().log(ex); + return nsrc; + } + } + + /** + * Small utility function creating li list from collection of urls + * @param remoteUrls + * @return + */ + public static String setOfUrlsToHtmlList(Iterable<URL> remoteUrls) { + StringBuilder sb = new StringBuilder(); + for (URL url : remoteUrls) { + sb.append("<li>").append(url.toExternalForm()).append("</li>"); + } + return sb.toString(); + } + + /** + * This function is removing all tailing slashes of url and + * both unix and windows salshes are supported. + * See tests for valid and invalid inputs/outputs + * Shortly protcol://som.url/some/path/ or protcol://som.url/some/path//// + * (and same for windows protcol://som.url/some\path\\) will become protcol://som.url/some/path + * Even protcol://som.url/ is reduced to protcol://som.url + * + * + * When input is like + * @param in + * @return + * @throws MalformedURLException + */ + public static URL sanitizeLastSlash(URL in) throws MalformedURLException { + if (in == null) { + return null; + } + String s = sanitizeLastSlash(in.toExternalForm()); + return new URL(s); + } + + public static String sanitizeLastSlash(final String in) { + if (in == null) { + return null; + } + String s = in; + while (s.endsWith("/") || s.endsWith("\\")) { + s = s.substring(0, s.length() - 1); + } + return s; + } + + /** + * both urls are processed by sanitizeLastSlash before actual equals. + * So protcol://som.url/some/path/ is same as protcol://som.url/some/path. + * Even protcol://som.url/some/path\ is same as protcol://som.url/some/path/ + * + * @param u1 + * @param u2 + * @return + */ + public static boolean equalsIgnoreLastSlash(URL u1, URL u2) { + try { + if (u1 == null && u2 == null) { + return true; + } + if (u1 == null && u2 != null) { + return false; + } + if (u1 != null && u2 == null) { + return false; + } + return sanitizeLastSlash(u1).equals(sanitizeLastSlash(u2)); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } }
--- a/tests/netx/unit/net/sourceforge/jnlp/util/ClasspathMatcherTest.java Thu Mar 13 13:48:00 2014 -0400 +++ b/tests/netx/unit/net/sourceforge/jnlp/util/ClasspathMatcherTest.java Fri Mar 14 13:06:03 2014 +0100 @@ -565,4 +565,102 @@ Assert.assertTrue(cps1.matches(new URL("http://whatever.anywher/"))); Assert.assertTrue(cps1.matches(new URL("http://whatever.anywher"))); } + + @Test + public void matchersTestWithPathsNix() throws MalformedURLException { + ClasspathMatchers cps1 = ClasspathMatcher.ClasspathMatchers.compile(" aa bb cc ", true); + ArrayList<ClasspathMatcher> q = cps1.getMatchers(); + Assert.assertEquals(3, q.size()); + Assert.assertEquals("aa", q.get(0).getParts().domain); + Assert.assertEquals("bb", q.get(1).getParts().domain); + Assert.assertEquals("cc", q.get(2).getParts().domain); + + + ClasspathMatchers cps2 = ClasspathMatcher.ClasspathMatchers.compile("http://aa.cz/xyz ftp://*bb.cz/bcq/dfg/aa*", true); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/aa"))); + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + //star + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq/aa-test.html"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg-aa-test.html"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg/aa-test.html"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg/aa"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg/aa/"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg/aa-files/aaa.jar"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg\\aa-files\\aaa.jar"))); + //double quotes may harm + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz//bcq/dfg/aa-files/aaa.jar"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz//bcq/dfg/aa-files//aaa.jar"))); + //no star + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz"))); + Assert.assertTrue(cps2.matches(new URL("http://aa.cz/xyz"))); + //double quotes may harm again + Assert.assertTrue(cps2.matches(new URL("http://aa.cz/xyz/"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz//xyz"))); + + + + } + + @Test + public void matchersTestWithPathsWin() throws MalformedURLException { + ClasspathMatchers cps1 = ClasspathMatcher.ClasspathMatchers.compile(" aa bb cc ", true); + ArrayList<ClasspathMatcher> q = cps1.getMatchers(); + Assert.assertEquals(3, q.size()); + Assert.assertEquals("aa", q.get(0).getParts().domain); + Assert.assertEquals("bb", q.get(1).getParts().domain); + Assert.assertEquals("cc", q.get(2).getParts().domain); + + + ClasspathMatchers cps2 = ClasspathMatcher.ClasspathMatchers.compile("http://aa.cz/xyz ftp://*bb.cz/bcq\\dfg\\aa*", true); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/aa"))); + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + //star + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq\\aa-test.html"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg-aa-test.html"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg\\aa-test.html"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg\\aa"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg\\aa\\"))); + Assert.assertTrue(cps2.matches(new URL("ftp://123.bb.cz/bcq\\dfg\\aa-files\\aaa.jar"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz/bcq/dfg/aa-files/aaa.jar"))); + //double quotes may harm + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz//bcq\\dfg\\aa-files\\aaa.jar"))); + Assert.assertFalse(cps2.matches(new URL("ftp://123.bb.cz//bcq\\dfg\\aa-files\\aaa.jar"))); + //no star + Assert.assertFalse(cps2.matches(new URL("http://bb.cz"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz"))); + Assert.assertTrue(cps2.matches(new URL("http://aa.cz/xyz"))); + //double quotes may harm again + Assert.assertTrue(cps2.matches(new URL("http://aa.cz/xyz\\"))); + Assert.assertFalse(cps2.matches(new URL("http://aa.cz//xyz"))); + + + + } + + @Test + public void trickyPathsMatchTes() throws MalformedURLException { + ClasspathMatchers cps1 = ClasspathMatcher.ClasspathMatchers.compile("http://aaa.com/some/path", true); + ClasspathMatchers cps11 = ClasspathMatcher.ClasspathMatchers.compile("http://aaa.com/some/path", false); + ClasspathMatchers cps2 = ClasspathMatcher.ClasspathMatchers.compile("http://aaa.com/some/path/", true); + ClasspathMatchers cps22 = ClasspathMatcher.ClasspathMatchers.compile("http://aaa.com/some/path/", false); + + Assert.assertTrue(cps1.matches(new URL("http://aaa.com/some/path"))); + Assert.assertTrue(cps1.matches(new URL("http://aaa.com/some/path/"))); + + Assert.assertFalse(cps2.matches(new URL("http://aaa.com/some/path"))); + Assert.assertTrue(cps2.matches(new URL("http://aaa.com/some/path/"))); + + + Assert.assertTrue(cps11.matches(new URL("http://aaa.com/some/path"))); + Assert.assertTrue(cps11.matches(new URL("http://aaa.com/some/path/"))); + + Assert.assertTrue(cps22.matches(new URL("http://aaa.com/some/path"))); + Assert.assertTrue(cps22.matches(new URL("http://aaa.com/some/path/"))); + + } }
--- a/tests/netx/unit/net/sourceforge/jnlp/util/UrlUtilsTest.java Thu Mar 13 13:48:00 2014 -0400 +++ b/tests/netx/unit/net/sourceforge/jnlp/util/UrlUtilsTest.java Fri Mar 14 13:06:03 2014 +0100 @@ -138,4 +138,111 @@ assertEquals(testFile, UrlUtils.decodeUrlAsFile(encodedUrl)); } } + + + @Test + public void testNormalizeUrlSlashStrings() throws Exception { + String u11 = UrlUtils.sanitizeLastSlash("http://aa.bb/aaa/bbb////"); + String u22 = UrlUtils.sanitizeLastSlash("http://aa.bb/aaa/bbb"); + assertEquals(u11, u22); + assertEquals(u11, "http://aa.bb/aaa/bbb"); + + String u1 = UrlUtils.sanitizeLastSlash(("http://aa.bb/aaa\\bbb\\")); + String u2 = UrlUtils.sanitizeLastSlash(("http://aa.bb/aaa\\bbb")); + assertEquals(u1, u2); + assertEquals(u1, ("http://aa.bb/aaa\\bbb")); + } + + @Test + public void testNormalizeUrlSlashUrls() throws Exception { + URL u11 = UrlUtils.sanitizeLastSlash(new URL("http://aa.bb/aaa/bbb////")); + URL u22 = UrlUtils.sanitizeLastSlash(new URL("http://aa.bb/aaa/bbb")); + assertEquals(u11, u22); + assertEquals(u11, new URL("http://aa.bb/aaa/bbb")); + + URL u1 = UrlUtils.sanitizeLastSlash(new URL("http://aa.bb/aaa\\bbb\\")); + URL u2 = UrlUtils.sanitizeLastSlash(new URL("http://aa.bb/aaa\\bbb")); + assertEquals(u1, u2); + assertEquals(u1, new URL("http://aa.bb/aaa\\bbb")); + } + + @Test + public void testEqualsIgnoreLastSlash() throws Exception { + URL u11 = (new URL("http://aa.bb/aaa/bbb////")); + URL u22 = (new URL("http://aa.bb/aaa/bbb")); + assertTrue(UrlUtils.equalsIgnoreLastSlash(u11, u22)); + assertTrue(UrlUtils.equalsIgnoreLastSlash(u11, new URL("http://aa.bb/aaa/bbb"))); + + URL u1 = (new URL("http://aa.bb/aaa\\bbb\\")); + URL u2 = (new URL("http://aa.bb/aaa\\bbb")); + assertTrue(UrlUtils.equalsIgnoreLastSlash(u1, u2)); + assertTrue(UrlUtils.equalsIgnoreLastSlash(u1, new URL("http://aa.bb/aaa\\bbb"))); + + assertTrue(UrlUtils.equalsIgnoreLastSlash(new URL("http://aa.bb/aaa\\bbb\\"), new URL("http://aa.bb/aaa\\bbb/"))); + assertFalse(UrlUtils.equalsIgnoreLastSlash(new URL("http://aa.bb/aaa\\bbb\\"), new URL("http://aa.bb/aaa/bbb/"))); + } + + @Test + public void removeFileName1() throws Exception { + URL l1 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz/hchkr/jar.jar")); + assertEquals(l1, new URL("http://aaa.bb/xyz/hchkr")); + + URL l2 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz/hchkr/")); + assertEquals(l2, new URL("http://aaa.bb/xyz/hchkr")); + + URL l3 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz/hchkr")); + assertEquals(l3, new URL("http://aaa.bb/xyz")); + + URL l4 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz/jar.jar")); + assertEquals(l4, new URL("http://aaa.bb/xyz")); + + URL l5 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz/")); + assertEquals(l5, new URL("http://aaa.bb/xyz")); + + URL l6 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz")); + assertEquals(l6, new URL("http://aaa.bb")); + + URL l7 = UrlUtils.removeFileName(new URL("http://aaa.bb/jar.jar")); + assertEquals(l7, new URL("http://aaa.bb")); + + URL l8 = UrlUtils.removeFileName(new URL("http://aaa.bb/")); + assertEquals(l8, new URL("http://aaa.bb")); + + URL l9 = UrlUtils.removeFileName(new URL("http://aaa.bb")); + assertEquals(l9, new URL("http://aaa.bb")); + + } + + public void removeFileName2() throws Exception { + URL l1 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz\\hchkr\\jar.jar")); + assertEquals(l1, new URL("http://aaa.bb/xyz\\hchkr")); + + URL l2 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz\\hchkr\\")); + assertEquals(l2, new URL("http://aaa.bb/xyz\\hchkr")); + + URL l3 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz\\hchkr")); + assertEquals(l3, new URL("http://aaa.bb/xyz")); + + URL l4 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz\\jar.jar")); + assertEquals(l4, new URL("http://aaa.bb/xyz")); + + URL l5 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz\\")); + assertEquals(l5, new URL("http://aaa.bb/xyz")); + + URL l6 = UrlUtils.removeFileName(new URL("http://aaa.bb/xyz")); + assertEquals(l6, new URL("http://aaa.bb")); + + URL l7 = UrlUtils.removeFileName(new URL("http://aaa.bb/jar.jar")); + assertEquals(l7, new URL("http://aaa.bb")); + + URL l8 = UrlUtils.removeFileName(new URL("http://aaa.bb/")); + assertEquals(l8, new URL("http://aaa.bb")); + + URL l9 = UrlUtils.removeFileName(new URL("http://aaa.bb")); + assertEquals(l9, new URL("http://aaa.bb")); + + } + + + }