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) =&gt; (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"));
+        
+    }
+     
+    
+
 }