2012-04-05 Jiri Vanek Fixing issue when process was not launched at all and when was killed but left behind living/hanging, fixing mime-types * tests/netx/jnlp_testsengine/net/sourceforge/jnlp/ServerAccess.java: (getContentOfStream) this method overloaded with possibility to specify encoding (I needed to set it to ASCII in one test) (deadlyException) field introduced in ThreadedProcess to record exception caused by impassibility of launching the process. And so process have been null without any sign why. (TinyHttpdImpl) now correctly returns known mime types (ProcessAssasin) can now skip or smoothly (and finally correctly) destroy its process, and all his logging messages were done null-proof (as deadlyException now allows) Asynchronous (ContentReader) have been silenced when complaining about closed streams by Assassin. 2012-03-30 Danesh Dadachanji Certificate start dates are not being checked, they are still verified even if the date has yet not been reached. * netx/net/sourceforge/jnlp/tools/JarSigner.java (verifyJar): If the start date is in the future, set notYetValidCert to true. 2012-03-14 Deepak Bhole Omair Majid PR895: IcedTea-Web searches for missing classes on each loadClass or findClass * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java (CodeBaseClassLoader): Added new map to track resources that are not found. (findClass): If resource was not found before, return immediately. If resource was not found for the first time, record it in the new map. (findResouces): Same. * tests/netx/unit/net/sourceforge/jnlp/runtime/CodeBaseClassLoaderTest.java: Test case for PR895 from Omair Majid. diff -r aac62fe468b1 -r 79b3ded39c1f netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java --- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Mon Mar 05 16:38:04 2012 -0500 +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Thu Apr 05 12:52:17 2012 +0200 @@ -37,6 +37,7 @@ import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -47,9 +48,11 @@ import java.util.Set; import java.util.TreeSet; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; + import net.sourceforge.jnlp.AppletDesc; import net.sourceforge.jnlp.ApplicationDesc; import net.sourceforge.jnlp.DownloadOptions; @@ -1874,6 +1877,11 @@ JNLPClassLoader parentJNLPClassLoader; + /** + * Classes that are not found, so that findClass can skip them next time + */ + ConcurrentHashMap notFoundResources = new ConcurrentHashMap(); + public CodeBaseClassLoader(URL[] urls, JNLPClassLoader cl) { super(urls); parentJNLPClassLoader = cl; @@ -1885,8 +1893,18 @@ } @Override - public Class findClass(String name) throws ClassNotFoundException { - return super.findClass(name); + public Class findClass(String name) throws ClassNotFoundException { + + // If we have searched this path before, don't try again + if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) + throw new ClassNotFoundException(name); + + try { + return super.findClass(name); + } catch (ClassNotFoundException cnfe) { + notFoundResources.put(name, super.getURLs()); + throw cnfe; + } } /** @@ -1913,17 +1931,41 @@ @Override public Enumeration findResources(String name) throws IOException { + + // If we have searched this path before, don't try again + if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) + return (new Vector(0)).elements(); + if (!name.startsWith("META-INF")) { - return super.findResources(name); + Enumeration urls = super.findResources(name); + + if (!urls.hasMoreElements()) { + notFoundResources.put(name, super.getURLs()); + } + + return urls; } + return (new Vector(0)).elements(); } @Override public URL findResource(String name) { + + // If we have searched this path before, don't try again + if (Arrays.equals(super.getURLs(), notFoundResources.get(name))) + return null; + if (!name.startsWith("META-INF")) { - return super.findResource(name); + URL url = super.findResource(name); + + if (url == null) { + notFoundResources.put(name, super.getURLs()); + } + + return url; } + return null; } } diff -r aac62fe468b1 -r 79b3ded39c1f netx/net/sourceforge/jnlp/tools/JarSigner.java --- a/netx/net/sourceforge/jnlp/tools/JarSigner.java Mon Mar 05 16:38:04 2012 -0500 +++ b/netx/net/sourceforge/jnlp/tools/JarSigner.java Thu Apr 05 12:52:17 2012 +0200 @@ -297,9 +297,15 @@ if (cert instanceof X509Certificate) { checkCertUsage((X509Certificate) cert, null); if (!showcerts) { + long notBefore = ((X509Certificate) cert) + .getNotBefore().getTime(); long notAfter = ((X509Certificate) cert) .getNotAfter().getTime(); + if (now < notBefore) { + notYetValidCert = true; + } + if (notAfter < now) { hasExpiredCert = true; } else if (notAfter < now + SIX_MONTHS) { diff -r aac62fe468b1 -r 79b3ded39c1f tests/netx/jnlp_testsengine/net/sourceforge/jnlp/ServerAccess.java --- a/tests/netx/jnlp_testsengine/net/sourceforge/jnlp/ServerAccess.java Mon Mar 05 16:38:04 2012 -0500 +++ b/tests/netx/jnlp_testsengine/net/sourceforge/jnlp/ServerAccess.java Thu Apr 05 12:52:17 2012 +0200 @@ -209,7 +209,7 @@ @Test public void testsProcessResultFiltering() throws Exception { - ProcessResult pn = new ProcessResult(null, null, null, true, 0); + ProcessResult pn = new ProcessResult(null, null, null, true, 0, null); Assert.assertNull(pn.notFilteredStdout); Assert.assertNull(pn.stdout); Assert.assertNull(pn.stderr); @@ -228,12 +228,12 @@ "test stage 1\n" + "test stage 2\n" + "test stage 3\n"; - ProcessResult p2 = new ProcessResult(fakeOut2, fakeOut2, null, true, 0); + ProcessResult p2 = new ProcessResult(fakeOut2, fakeOut2, null, true, 0, null); Assert.assertEquals(p2.notFilteredStdout, fakeOut2); Assert.assertEquals(p2.stdout, filteredOut2); Assert.assertEquals(p2.stderr, fakeOut2); fakeOut2+="\n"; - p2 = new ProcessResult(fakeOut2, fakeOut2, null, true, 0); + p2 = new ProcessResult(fakeOut2, fakeOut2, null, true, 0, null); Assert.assertEquals(p2.notFilteredStdout, fakeOut2); Assert.assertEquals(p2.stdout, filteredOut2); Assert.assertEquals(p2.stderr, fakeOut2); @@ -255,13 +255,13 @@ + "test stage 2\n" + "test stage 3\n" + "test ends"; - ProcessResult p = new ProcessResult(fakeOut, fakeOut, null, true, 0); + ProcessResult p = new ProcessResult(fakeOut, fakeOut, null, true, 0, null); Assert.assertEquals(p.notFilteredStdout, fakeOut); Assert.assertEquals(p.stdout, filteredOut); Assert.assertEquals(p.stderr, fakeOut); fakeOut+="\n"; filteredOut+="\n"; - p = new ProcessResult(fakeOut, fakeOut, null, true, 0); + p = new ProcessResult(fakeOut, fakeOut, null, true, 0, null); Assert.assertEquals(p.notFilteredStdout, fakeOut); Assert.assertEquals(p.stdout, filteredOut); Assert.assertEquals(p.stderr, fakeOut); @@ -421,9 +421,9 @@ * @return stream as string * @throws IOException if connection cant be established or resource do not exists */ - public static String getContentOfStream(InputStream is) throws IOException { + public static String getContentOfStream(InputStream is,String encoding) throws IOException { try { - BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); + BufferedReader br = new BufferedReader(new InputStreamReader(is, encoding)); StringBuilder sb = new StringBuilder(); while (true) { String s = br.readLine(); @@ -441,6 +441,18 @@ } /** + * utility method which can read from any stream as one long String + * + * @param input stream + * @return stream as string + * @throws IOException if connection cant be established or resource do not exists + */ + public static String getContentOfStream(InputStream is) throws IOException { + return getContentOfStream(is, "UTF-8"); + + } + + /** * utility method which can read bytes of resource from any url * * @param resource to be located on any url @@ -601,9 +613,13 @@ ProcessAssasin pa = new ProcessAssasin(t, PROCESS_TIMEOUT); pa.start(); t.start(); - while (t.getP() == null) { + while (t.getP() == null && t.deadlyException == null) { Thread.sleep(100); } + if (t.deadlyException != null) { + pa.setCanRun(false); + return new ProcessResult("", "", null, true, Integer.MIN_VALUE, t.deadlyException); + } ContentReader crs = new ContentReader(t.getP().getInputStream()); ContentReader cre = new ContentReader(t.getP().getErrorStream()); @@ -617,10 +633,14 @@ while (t.isRunning()) { Thread.sleep(100); } + + while (!t.isDestoyed()) { + Thread.sleep(100); + } pa.setCanRun(false); // System.out.println(t.getP().exitValue()); when process is killed, this throws exception - return new ProcessResult(crs.getContent(), cre.getContent(), t.getP(), pa.wasTerminated(), t.getExitCode()); + return new ProcessResult(crs.getContent(), cre.getContent(), t.getP(), pa.wasTerminated(), t.getExitCode(), null); } /** @@ -635,6 +655,20 @@ Integer exitCode; Boolean running; File dir; + Throwable deadlyException = null; + /* + * before removing this "useless" variable + * check DeadLockTestTest.testDeadLockTestTerminated2 + */ + private boolean destoyed = false; + + public boolean isDestoyed() { + return destoyed; + } + + public void setDestoyed(boolean destoyed) { + this.destoyed = destoyed; + } public Boolean isRunning() { return running; @@ -683,12 +717,21 @@ }else{ p = r.exec(args.toArray(new String[0]),new String[0], dir); } - exitCode = p.waitFor(); + try { + exitCode = p.waitFor(); + Thread.sleep(500);//this is giving to fastly done proecesses's e/o readers time to read all. I would like to know better solution :-/ + } finally { + destoyed = true; + } } catch (Exception ex) { if (ex instanceof InterruptedException) { //add to the set of terminated threadedproceses + deadlyException = ex; terminated.add(this); } else { + //happens when nonexisting process is launched, is causing p null! + terminated.add(this); + deadlyException = ex; throw new RuntimeException(ex); } } finally { @@ -828,8 +871,17 @@ byte[] b = new byte[l]; FileInputStream f = new FileInputStream(pp); f.read(b); + String content = ""; + String ct = "Content-Type: "; + if (p.toLowerCase().endsWith(".jnlp")) { + content = ct + "application/x-java-jnlp-file\n"; + } else if (p.toLowerCase().endsWith(".html")) { + content = ct + "text/html\n"; + } else if (p.toLowerCase().endsWith(".jar")) { + content = ct + "application/x-jar\n"; + } o.writeBytes("HTTP/1.0 200 OK\nConten" - + "t-Length:" + l + "\n\n"); + + "t-Length:" + l + "\n" + content + "\n"); o.write(b, 0, l); } } @@ -855,6 +907,13 @@ //false == is disabled:( private boolean canRun = true; private boolean wasTerminated = false; + /** + * if this is true, then process is not destroyed after timeout, but just left to its own destiny. + * Its stdout/err is no longer recorded, and it is leaking system resources until it dies by itself + * The contorl is returned to main thread with all informations recorded untill now. + * You will be able to listen to std out from listeners still + */ + private boolean skipInstedOfDesroy = false; /** * @@ -868,9 +927,25 @@ } + public ProcessAssasin(ThreadedProcess p, long timeout, boolean skipInstedOfDesroy) { + this.p = (p); + this.timeout = timeout; + this.skipInstedOfDesroy = skipInstedOfDesroy; + + + } + public void setCanRun(boolean canRun) { this.canRun = canRun; - System.err.println("Stopping assasin for" + p.toString() + " " + p.getP().toString() + " " + p.getCommandLine() + ": "); + if (p != null) { + if (p.getP() != null) { + System.err.println("Stopping assassin for" + p.toString() + " " + p.getP().toString() + " " + p.getCommandLine() + ": "); + } else { + System.err.println("Stopping assassin for" + p.toString() + " " + p.getCommandLine() + ": "); + } + } else { + System.err.println("Stopping assassin for null job: "); + } System.err.flush(); } @@ -882,6 +957,14 @@ return wasTerminated; } + public void setSkipInstedOfDesroy(boolean skipInstedOfDesroy) { + this.skipInstedOfDesroy = skipInstedOfDesroy; + } + + public boolean isSkipInstedOfDesroy() { + return skipInstedOfDesroy; + } + @Override public void run() { @@ -893,16 +976,44 @@ //System.out.println(time - startTime); //System.out.println((time - startTime) > timeout); if ((time - startTime) > timeout) { - System.err.println("Timeouted " + p.toString() + " " + p.getP().toString() + " .. killing " + p.getCommandLine() + ": "); - System.err.flush(); - wasTerminated = true; - p.interrupt(); - while (!terminated.contains(p)) { - Thread.sleep(100); + try { + if (p != null) { + if (p.getP() != null) { + System.err.println("Timed out " + p.toString() + " " + p.getP().toString() + " .. killing " + p.getCommandLine() + ": "); + } else { + System.err.println("Timed out " + p.toString() + " " + "null .. killing " + p.getCommandLine() + ": "); + } + System.err.flush(); + wasTerminated = true; + p.interrupt(); + while (!terminated.contains(p)) { + Thread.sleep(100); + } + if (p.getP() != null) { + try { + if (!skipInstedOfDesroy) { + p.getP().destroy(); + } + } catch (Throwable ex) { + if (p.deadlyException == null) { + p.deadlyException = ex; + } + ex.printStackTrace(); + } + } + if (p.getP() != null) { + System.err.println("Timed out " + p.toString() + " " + p.getP().toString() + " .. killed " + p.getCommandLine()); + } else { + System.err.println("Timed out " + p.toString() + " null .. killed " + p.getCommandLine()); + } + System.err.flush(); + } else { + System.err.println("Timed out null job"); + } + break; + } finally { + p.setDestoyed(true); } - System.err.println("Timeouted " + p.toString() + " " + p.getP().toString() + " .. killed " + p.getCommandLine()); - System.err.flush(); - break; } @@ -911,7 +1022,15 @@ ex.printStackTrace(); } } - System.err.println("assasin for" + p.toString() + " " + p.getP().toString() + " .. done " + p.getCommandLine() + " termination " + wasTerminated); + if (p != null) { + if (p.getP() != null) { + System.err.println("assassin for" + p.toString() + " " + p.getP().toString() + " .. done " + p.getCommandLine() + " termination " + wasTerminated); + } else { + System.err.println("assassin for" + p.toString() + " null .. done " + p.getCommandLine() + " termination " + wasTerminated); + } + } else { + System.err.println("assassin for non exisitng job termination " + wasTerminated); + } System.err.flush(); } } @@ -927,8 +1046,12 @@ public final Process process; public final Integer returnValue; public final boolean wasTerminated; + /* + * possible exception which caused Process not to be launched + */ + public final Throwable deadlyException; - public ProcessResult(String stdout, String stderr, Process process, boolean wasTerminated, Integer r) { + public ProcessResult(String stdout, String stderr, Process process, boolean wasTerminated, Integer r, Throwable deadlyException) { this.notFilteredStdout = stdout; if (stdout == null) { this.stdout = null; @@ -939,6 +1062,7 @@ this.process = process; this.wasTerminated = wasTerminated; this.returnValue = r; + this.deadlyException = deadlyException; } } @@ -991,8 +1115,10 @@ } //do not want to bother output with terminations + //mostly compaling when assassin kill the process about StreamClosed } catch (Exception ex) { - //ex.printStackTrace(); + // ex.printStackTrace(); + // System.err.flush(); } finally { try { is.close(); diff -r aac62fe468b1 -r 79b3ded39c1f tests/netx/unit/net/sourceforge/jnlp/runtime/CodeBaseClassLoaderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/netx/unit/net/sourceforge/jnlp/runtime/CodeBaseClassLoaderTest.java Thu Apr 05 12:52:17 2012 +0200 @@ -0,0 +1,142 @@ +/* CodeBaseClassLoaderTest.java + Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +package net.sourceforge.jnlp.runtime; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.Locale; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.ParseException; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.SecurityDesc; +import net.sourceforge.jnlp.runtime.JNLPClassLoader; +import net.sourceforge.jnlp.runtime.JNLPClassLoader.CodeBaseClassLoader; + +import org.junit.Test; + +public class CodeBaseClassLoaderTest { + + @Test + public void testResourceLoadSuccessCaching() throws LaunchException, ClassNotFoundException, IOException, ParseException { + final URL JAR_URL = new URL("http://icedtea.classpath.org/netx/about.jar"); + final URL CODEBASE_URL = new URL("http://icedtea.classpath.org/netx/"); + + JNLPFile dummyJnlpFile = new JNLPFile() { + @Override + public ResourcesDesc getResources() { + return new ResourcesDesc(null, new Locale[0], new String[0], new String[0]); + } + + @Override + public URL getCodeBase() { + return CODEBASE_URL; + } + + @Override + public SecurityDesc getSecurity() { + return new SecurityDesc(null, SecurityDesc.SANDBOX_PERMISSIONS, null); + } + }; + + JNLPClassLoader parent = new JNLPClassLoader(dummyJnlpFile, null); + CodeBaseClassLoader classLoader = new CodeBaseClassLoader(new URL[] { JAR_URL, CODEBASE_URL }, parent); + + long startTime, stopTime; + + startTime = System.nanoTime(); + classLoader.findResource("net/sourceforge/jnlp/about/Main.class"); + stopTime = System.nanoTime(); + long timeOnFirstTry = stopTime - startTime; + System.out.println(timeOnFirstTry); + + startTime = System.nanoTime(); + classLoader.findResource("net/sourceforge/jnlp/about/Main.class"); + stopTime = System.nanoTime(); + long timeOnSecondTry = stopTime - startTime; + System.out.println(timeOnSecondTry); + + assertTrue(timeOnSecondTry < (timeOnFirstTry / 10)); + } + + @Test + public void testResourceLoadFailureCaching() throws LaunchException, ClassNotFoundException, IOException, ParseException { + final URL JAR_URL = new URL("http://icedtea.classpath.org/netx/about.jar"); + final URL CODEBASE_URL = new URL("http://icedtea.classpath.org/netx/"); + + JNLPFile dummyJnlpFile = new JNLPFile() { + @Override + public ResourcesDesc getResources() { + return new ResourcesDesc(null, new Locale[0], new String[0], new String[0]); + } + + @Override + public URL getCodeBase() { + return CODEBASE_URL; + } + + @Override + public SecurityDesc getSecurity() { + return new SecurityDesc(null, SecurityDesc.SANDBOX_PERMISSIONS, null); + } + }; + + JNLPClassLoader parent = new JNLPClassLoader(dummyJnlpFile, null); + CodeBaseClassLoader classLoader = new CodeBaseClassLoader(new URL[] { JAR_URL, CODEBASE_URL }, parent); + + long startTime, stopTime; + + startTime = System.nanoTime(); + classLoader.findResource("net/sourceforge/jnlp/about/Main_FOO_.class"); + stopTime = System.nanoTime(); + long timeOnFirstTry = stopTime - startTime; + System.out.println(timeOnFirstTry); + + startTime = System.nanoTime(); + classLoader.findResource("net/sourceforge/jnlp/about/Main_FOO_.class"); + stopTime = System.nanoTime(); + long timeOnSecondTry = stopTime - startTime; + System.out.println(timeOnSecondTry); + + assertTrue(timeOnSecondTry < (timeOnFirstTry / 10)); + } + +}