diff --git a/LICENSE.txt b/LICENSE.txt index 91006b4da4..7d5a6b828f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -177,10 +177,11 @@ Applications: By welterde. See licenses/LICENSE-GPLv2.txt - Jetty 6.1.26: - Copyright 1995-2009 Mort Bay Consulting Pty Ltd - See licenses/LICENSE-Jetty.txt + Jetty 7.6.8.v20121106: + See licenses/ABOUT-Jetty.html + See licenses/NOTICE-Jetty.html See licenses/LICENSE-Apache2.0.txt + See licenses/LICENSE-ECLIPSE-1.0.html See licenses/NOTICE-Commons-Logging.txt JRobin 1.5.9.1: diff --git a/apps/i2psnark/java/build.xml b/apps/i2psnark/java/build.xml index fddfe4b51d..025c3f1db9 100644 --- a/apps/i2psnark/java/build.xml +++ b/apps/i2psnark/java/build.xml @@ -19,6 +19,7 @@ + @@ -38,7 +39,7 @@ debug="true" deprecation="on" source="1.5" target="1.5" destdir="./build/obj" includeAntRuntime="false" - classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../jetty/jettylib/jetty-util.jar:../../ministreaming/java/build/mstreaming.jar" > + classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../jetty/jettylib/jetty-servlet.jar:../../jetty/jettylib/jetty-util.jar:../../ministreaming/java/build/mstreaming.jar" > @@ -103,9 +104,11 @@ + + - + diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 89ab2a01a7..6ea7498b4a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -49,6 +49,7 @@ import org.klomp.snark.dht.KRPC; public class I2PSnarkUtil { private final I2PAppContext _context; private final Log _log; + private final String _baseName; private boolean _shouldProxy; private String _proxyHost; @@ -82,8 +83,17 @@ public class I2PSnarkUtil { public static final boolean DEFAULT_USE_DHT = true; public I2PSnarkUtil(I2PAppContext ctx) { + this(ctx, "i2psnark"); + } + + /** + * @param baseName generally "i2psnark" + * @since Jetty 7 + */ + public I2PSnarkUtil(I2PAppContext ctx, String baseName) { _context = ctx; _log = _context.logManager().getLog(Snark.class); + _baseName = baseName; _opts = new HashMap(); //setProxy("127.0.0.1", 4444); setI2CPConfig("127.0.0.1", 7654, null); @@ -99,7 +109,7 @@ public class I2PSnarkUtil { // This is used for both announce replies and .torrent file downloads, // so it must be available even if not connected to I2CP. // so much for multiple instances - _tmpDir = new SecureDirectory(ctx.getTempDir(), "i2psnark"); + _tmpDir = new SecureDirectory(ctx.getTempDir(), baseName); FileUtil.rmdir(_tmpDir, false); _tmpDir.mkdirs(); } @@ -253,7 +263,7 @@ public class I2PSnarkUtil { _connecting = false; } if (_shouldUseDHT && _manager != null && _dht == null) - _dht = new KRPC(_context, _manager.getSession()); + _dht = new KRPC(_context, _baseName, _manager.getSession()); return (_manager != null); } @@ -588,7 +598,7 @@ public class I2PSnarkUtil { public synchronized void setUseDHT(boolean yes) { _shouldUseDHT = yes; if (yes && _manager != null && _dht == null) { - _dht = new KRPC(_context, _manager.getSession()); + _dht = new KRPC(_context, _baseName, _manager.getSession()); } else if (!yes && _dht != null) { _dht.stop(); _dht = null; diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 2b2338b45a..36b099f386 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -57,6 +57,8 @@ public class SnarkManager implements CompleteListener { private /* FIXME final FIXME */ File _configFile; private Properties _config; private final I2PAppContext _context; + private final String _contextPath; + private final String _contextName; private final Log _log; private final Queue _messages; private final I2PSnarkUtil _util; @@ -82,7 +84,7 @@ public class SnarkManager implements CompleteListener { public static final String PROP_META_PRIORITY_SUFFIX = ".priority"; public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet."; - private static final String CONFIG_FILE = "i2psnark.config"; + private static final String CONFIG_FILE_SUFFIX = ".config"; public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic"; public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops public static final String DEFAULT_AUTO_START = "false"; @@ -128,17 +130,24 @@ public class SnarkManager implements CompleteListener { /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */ public static final String PROP_TRACKERS = "i2psnark.trackers"; - public SnarkManager(I2PAppContext ctx) { + /** + * @param ctxPath generally "/i2psnark" + * @param ctxName generally "i2psnark" + */ + public SnarkManager(I2PAppContext ctx, String ctxPath, String ctxName) { _snarks = new ConcurrentHashMap(); _magnets = new ConcurrentHashSet(); _addSnarkLock = new Object(); _context = ctx; + _contextPath = ctxPath; + _contextName = ctxName; _log = _context.logManager().getLog(SnarkManager.class); _messages = new LinkedBlockingQueue(); - _util = new I2PSnarkUtil(_context); - _configFile = new File(CONFIG_FILE); + _util = new I2PSnarkUtil(_context, ctxName); + String cfile = ctxName + CONFIG_FILE_SUFFIX; + _configFile = new File(cfile); if (!_configFile.isAbsolute()) - _configFile = new File(_context.getConfigDir(), CONFIG_FILE); + _configFile = new File(_context.getConfigDir(), cfile); _trackerMap = new ConcurrentHashMap(4); loadConfig(null); } @@ -259,7 +268,7 @@ public class SnarkManager implements CompleteListener { } public File getDataDir() { - String dir = _config.getProperty(PROP_DIR, "i2psnark"); + String dir = _config.getProperty(PROP_DIR, _contextName); File f; if (areFilesPublic()) f = new File(dir); @@ -305,7 +314,7 @@ public class SnarkManager implements CompleteListener { if (!_config.containsKey(PROP_UPLOADERS_TOTAL)) _config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS); if (!_config.containsKey(PROP_DIR)) - _config.setProperty(PROP_DIR, "i2psnark"); + _config.setProperty(PROP_DIR, _contextName); if (!_config.containsKey(PROP_AUTO_START)) _config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START); if (!_config.containsKey(PROP_REFRESH_DELAY)) @@ -731,6 +740,11 @@ public class SnarkManager implements CompleteListener { public Properties getConfig() { return _config; } + /** @since Jetty 7 */ + public String getConfigFilename() { + return _configFile.getAbsolutePath(); + } + /** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */ public static final int MAX_FILES_PER_TORRENT = 512; @@ -1445,7 +1459,7 @@ public class SnarkManager implements CompleteListener { if (meta == null || storage == null) return; StringBuilder buf = new StringBuilder(256); - buf.append("").append(storage.getBaseName()).append(""); diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java index b94c5f555f..c0be11182e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java @@ -152,12 +152,15 @@ public class KRPC implements I2PSessionMuxedListener, DHT { private static final long CLEAN_TIME = 63*1000; private static final long EXPLORE_TIME = 877*1000; private static final long BLACKLIST_CLEAN_TIME = 17*60*1000; - private static final String DHT_FILE = "i2psnark.dht.dat"; + private static final String DHT_FILE_SUFFIX = ".dht.dat"; private static final int SEND_CRYPTO_TAGS = 8; private static final int LOW_CRYPTO_TAGS = 4; - public KRPC (I2PAppContext ctx, I2PSession session) { + /** + * @param baseName generally "i2psnark" + */ + public KRPC (I2PAppContext ctx, String baseName, I2PSession session) { _context = ctx; _session = session; _log = ctx.logManager().getLog(KRPC.class); @@ -182,7 +185,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { _myNID = new NID(_myID); } _myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort); - _dhtFile = new File(ctx.getConfigDir(), DHT_FILE); + _dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX); _knownNodes = new DHTNodes(ctx, _myNID); start(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java new file mode 100644 index 0000000000..8577460500 --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java @@ -0,0 +1,550 @@ +// ======================================================================== +// Copyright 199-2004 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ======================================================================== + +package org.klomp.snark.web; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.i2p.I2PAppContext; +import net.i2p.data.ByteArray; +import net.i2p.util.ByteCache; +import net.i2p.util.Log; + + +/* ------------------------------------------------------------ */ +/** + * Based on DefaultServlet from Jetty 6.1.26, heavily simplified + * and modified to remove all dependencies on Jetty libs. + * + * Supports HEAD and GET only, for resources from the .war and local files. + * Supports files and resource only. + * Supports MIME types with local overrides and additions. + * Supports Last-Modified. + * + * Does not support directories or "welcome files". + * Does not support gzip. + * Does not support request ranges. + * Does not cache. + * + * HEAD and POST return 405. + * Directories return 403. + * Jar resources are sent with a long cache directive. + * + * ------------------------------------------------------------ + * + * The default servlet. + * This servlet, normally mapped to /, provides the handling for static + * content, OPTION and TRACE methods for the context. + * The following initParameters are supported, these can be set either + * on the servlet itself or as ServletContext initParameters with a prefix + * of org.mortbay.jetty.servlet.Default. : + *
                                                                      
+ *
+ *  resourceBase      Set to replace the context resource base
+
+ *  warBase      Path allowed for resource in war
+ * 
+ * 
+ * + * + * @author Greg Wilkins (gregw) + * @author Nigel Canonizado + * + * @since Jetty 7 + */ +class BasicServlet extends HttpServlet +{ + protected final I2PAppContext _context; + protected final Log _log; + protected File _resourceBase; + private String _warBase; + + private final MimeTypes _mimeTypes; + + /** same as PeerState.PARTSIZE */ + private static final int BUFSIZE = 16*1024; + private ByteCache _cache = ByteCache.getInstance(16, BUFSIZE); + + private static final int WAR_CACHE_CONTROL_SECS = 24*60*60; + private static final int FILE_CACHE_CONTROL_SECS = 24*60*60; + + public BasicServlet() { + super(); + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(getClass()); + _mimeTypes = new MimeTypes(); + } + + /* ------------------------------------------------------------ */ + public void init(ServletConfig cfg) throws ServletException { + super.init(cfg); + String rb=getInitParameter("resourceBase"); + if (rb!=null) + { + File f = new File(rb); + setResourceBase(f); + } + String wb = getInitParameter("warBase"); + if (wb != null) + setWarBase(wb); + } + + /** + * Files are served from here + */ + protected void setResourceBase(File base) throws UnavailableException { + if (!base.isDirectory()) + throw new UnavailableException("Resource base does not exist: " + base); + _resourceBase = base; + if (_log.shouldLog(Log.INFO)) + _log.info("Resource base is " + _resourceBase); + } + + /** + * Only paths starting with this in the path are served + */ + protected void setWarBase(String base) { + if (!base.startsWith("/")) + base = '/' + base; + if (!base.endsWith("/")) + base = base + '/'; + _warBase = base; + if (_log.shouldLog(Log.INFO)) + _log.info("War base is " + _warBase); + } + + /** get Resource to serve. + * Map a path to a resource. The default implementation calls + * HttpContext.getResource but derived servlets may provide + * their own mapping. + * @param pathInContext The path to find a resource for. + * @return The resource to serve or null if not existing + */ + public File getResource(String pathInContext) + { + if (_resourceBase==null) + return null; + File r = null; + if (!pathInContext.contains("..") && + !pathInContext.endsWith("/")) { + File f = new File(_resourceBase, pathInContext); + if (f.exists()) + r = f; + } + return r; + } + + /** get Resource to serve. + * Map a path to a resource. The default implementation calls + * HttpContext.getResource but derived servlets may provide + * their own mapping. + * @param pathInContext The path to find a resource for. + * @return The resource to serve or null. Returns null for directories + */ + public HttpContent getContent(String pathInContext) + { + if (_resourceBase==null) + return null; + HttpContent r = null; + if (_warBase != null && pathInContext.startsWith(_warBase)) { + r = new JarContent(pathInContext); + } else if (!pathInContext.contains("..") && + !pathInContext.endsWith("/")) { + File f = new File(_resourceBase, pathInContext); + // exists && !directory + if (f.isFile()) + r = new FileContent(f); + } + return r; + } + + /* ------------------------------------------------------------ */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + // always starts with a '/' + String servletpath = request.getServletPath(); + String pathInfo=request.getPathInfo(); + // ??? right?? + String pathInContext = addPaths(servletpath, pathInfo); + + // Find the resource and content + try { + HttpContent content = getContent(pathInContext); + + // Handle resource + if (content == null) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Not found: " + pathInContext); + response.sendError(404); + } else { + if (passConditionalHeaders(request, response, content)) { + if (_log.shouldLog(Log.INFO)) + _log.info("Sending: " + content); + sendData(request, response, content); + } else { + if (_log.shouldLog(Log.INFO)) + _log.info("Not modified: " + content); + } + } + } + catch(IllegalArgumentException e) + { + if (_log.shouldLog(Log.WARN)) + _log.warn("Error sending " + pathInContext, e); + if(!response.isCommitted()) + response.sendError(500, e.getMessage()); + } + catch(IOException e) + { + if (_log.shouldLog(Log.WARN)) + // typical browser abort + //_log.warn("Error sending", e); + _log.warn("Error sending " + pathInContext + ": " + e); + throw e; + } + } + + /* ------------------------------------------------------------ */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(405); + } + + /* ------------------------------------------------------------ */ + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + protected void doTrace(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(405); + } + + protected void doOptions(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(405); + } + + protected void doDelete(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + response.sendError(405); + } + + + /* ------------------------------------------------------------ */ + /** Check modification date headers. + * @return true to keep going, false if handled here + */ + protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content) + throws IOException + { + try + { + if (!request.getMethod().equals("HEAD") ) { + long ifmsl=request.getDateHeader("If-Modified-Since"); + if (ifmsl!=-1) + { + if (content.getLastModified()/1000 <= ifmsl/1000) + { + response.reset(); + response.setStatus(304); + response.flushBuffer(); + return false; + } + } + } + } + catch(IllegalArgumentException iae) + { + if(!response.isCommitted()) + response.sendError(400, iae.getMessage()); + throw iae; + } + return true; + } + + + /* ------------------------------------------------------------ */ + protected void sendData(HttpServletRequest request, + HttpServletResponse response, + HttpContent content) + throws IOException + { + InputStream in =null; + try { + in = content.getInputStream(); + } catch (IOException e) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Not found: " + content); + response.sendError(404); + return; + } + + OutputStream out =null; + try { + out = response.getOutputStream(); + } catch (IllegalStateException e) { + out = new WriterOutputStream(response.getWriter()); + } + + // Write content normally + long content_length = content.getContentLength(); + writeHeaders(response,content,content_length); + if (content_length >= 0 && request.getMethod().equals("HEAD")) { + // if we know the content length, don't send it to be counted + if (_log.shouldLog(Log.INFO)) + _log.info("HEAD: " + content); + } else { + // GET or unknown size for HEAD + copy(in, out); + } + + return; + } + + /* ------------------------------------------------------------ */ + protected void writeHeaders(HttpServletResponse response,HttpContent content,long count) + throws IOException + { + if (content.getContentType()!=null && response.getContentType()==null) + response.setContentType(content.getContentType()); + + long lml = content.getLastModified(); + if (lml > 0) + response.setDateHeader("Last-Modified",lml); + + if (count != -1) + { + if (count=0) + response.setHeader("Cache-Control", "public, max-age=" + ct); + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* I2P additions below here */ + + /** from Jetty HttpContent.java */ + public interface HttpContent + { + String getContentType(); + long getLastModified(); + /** in seconds */ + int getCacheTime(); + long getContentLength(); + InputStream getInputStream() throws IOException; + } + + private class FileContent implements HttpContent + { + private final File _file; + + public FileContent(File file) + { + _file = file; + } + + /* ------------------------------------------------------------ */ + public String getContentType() + { + //return _mimeTypes.getMimeByExtension(_file.toString()); + return getMimeType(_file.toString()); + } + + /* ------------------------------------------------------------ */ + public long getLastModified() + { + return _file.lastModified(); + } + + public int getCacheTime() + { + return FILE_CACHE_CONTROL_SECS; + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return _file.length(); + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + return new BufferedInputStream(new FileInputStream(_file)); + } + + @Override + public String toString() { return "File \"" + _file + '"'; } + } + + private class JarContent implements HttpContent + { + private final String _path; + + public JarContent(String path) + { + _path = path; + } + + /* ------------------------------------------------------------ */ + public String getContentType() + { + return getMimeType(_path); + } + + /* ------------------------------------------------------------ */ + public long getLastModified() + { + String cpath = getServletContext().getContextPath(); + // this won't work if we aren't at top level + String cname = cpath == "" ? "i2psnark" : cpath.substring(1).replace("/", "_"); + return (new File(_context.getBaseDir(), "webapps/" + cname + ".war")).lastModified(); + } + + public int getCacheTime() + { + return WAR_CACHE_CONTROL_SECS; + } + + /* ------------------------------------------------------------ */ + public long getContentLength() + { + return -1; + } + + /* ------------------------------------------------------------ */ + public InputStream getInputStream() throws IOException + { + InputStream rv = getServletContext().getResourceAsStream(_path); + if (rv == null) + throw new IOException("Not found"); + return rv; + } + + @Override + public String toString() { return "Jar resource \"" + _path + '"'; } + } + + + /** + * @param resourcePath in the classpath, without ".properties" extension + */ + protected void loadMimeMap(String resourcePath) { + _mimeTypes.loadMimeMap(resourcePath); + } + + /* ------------------------------------------------------------ */ + /** Get the MIME type by filename extension. + * @param filename A file name + * @return MIME type matching the longest dot extension of the + * file name. + */ + protected String getMimeType(String filename) { + String rv = _mimeTypes.getMimeByExtension(filename); + if (rv != null) + return rv; + return getServletContext().getMimeType(filename); + } + + protected void addMimeMapping(String extension, String type) { + _mimeTypes.addMimeMapping(extension, type); + } + + /** + * Simple version of URIUtil.addPaths() + * @param path may be null + */ + protected static String addPaths(String base, String path) { + if (path == null) + return base; + return (new File(base, path)).toString(); + } + + /** + * Simple version of URIUtil.decodePath() + */ + protected static String decodePath(String path) throws MalformedURLException { + if (!path.contains("%")) + return path; + try { + URI uri = new URI(path); + return uri.getPath(); + } catch (URISyntaxException use) { + // for ease of use, since a USE is not an IOE but a MUE is... + throw new MalformedURLException(use.getMessage()); + } + } + + /** + * Simple version of URIUtil.encodePath() + */ + protected static String encodePath(String path) throws MalformedURLException { + try { + URI uri = new URI(null, null, path, null); + return uri.toString(); + } catch (URISyntaxException use) { + // for ease of use, since a USE is not an IOE but a MUE is... + throw new MalformedURLException(use.getMessage()); + } + } + + /** + * Write from in to out + */ + private void copy(InputStream in, OutputStream out) throws IOException { + ByteArray ba = _cache.acquire(); + byte[] buf = ba.getData(); + try { + int read = 0; + while ( (read = in.read(buf)) != -1) { + out.write(buf, 0, read); + } + } finally { + _cache.release(ba, false); + if (in != null) + try { in.close(); } catch (IOException ioe) {} + if (out != null) + try { out.close(); } catch (IOException ioe) {} + } + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index ce93fb93ec..518d1ef3a3 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -24,6 +24,7 @@ import java.util.TreeSet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -44,42 +45,48 @@ import org.klomp.snark.Tracker; import org.klomp.snark.TrackerClient; import org.klomp.snark.dht.DHT; -import org.mortbay.jetty.servlet.DefaultServlet; -import org.mortbay.resource.Resource; -import org.mortbay.util.URIUtil; - /** - * We extend Default instead of HTTPServlet so we can handle - * i2psnark/ file requests with http:// instead of the flaky and - * often-blocked-by-the-browser file:// + * Refactored to eliminate Jetty dependencies. */ -public class I2PSnarkServlet extends DefaultServlet { - private I2PAppContext _context; - private Log _log; +public class I2PSnarkServlet extends BasicServlet { + /** generally "/i2psnark" */ + private String _contextPath; + /** generally "i2psnark" */ + private String _contextName; private SnarkManager _manager; private static long _nonce; - private Resource _resourceBase; private String _themePath; private String _imgPath; private String _lastAnnounceURL; + private static final String DEFAULT_NAME = "i2psnark"; public static final String PROP_CONFIG_FILE = "i2psnark.configFile"; + public I2PSnarkServlet() { + super(); + } + @Override public void init(ServletConfig cfg) throws ServletException { - _context = I2PAppContext.getGlobalContext(); - _log = _context.logManager().getLog(I2PSnarkServlet.class); + super.init(cfg); + String cpath = getServletContext().getContextPath(); + _contextPath = cpath == "" ? "/" : cpath; + _contextName = cpath == "" ? DEFAULT_NAME : cpath.substring(1).replace("/", "_"); _nonce = _context.random().nextLong(); - _manager = new SnarkManager(_context); + // limited protection against overwriting other config files or directories + // in case you named your war "router.war" + String configName = _contextName; + if (!configName.equals(DEFAULT_NAME)) + configName = DEFAULT_NAME + '_' + _contextName; + _manager = new SnarkManager(_context, _contextPath, configName); String configFile = _context.getProperty(PROP_CONFIG_FILE); if ( (configFile == null) || (configFile.trim().length() <= 0) ) - configFile = "i2psnark.config"; + configFile = configName + ".config"; _manager.loadConfig(configFile); _manager.start(); - try { - _resourceBase = Resource.newResource(_manager.getDataDir().getAbsolutePath()); - } catch (IOException ioe) {} - super.init(cfg); + loadMimeMap("org/klomp/snark/web/mime"); + setResourceBase(_manager.getDataDir()); + setWarBase("/.icons/"); } @Override @@ -95,29 +102,36 @@ public class I2PSnarkServlet extends DefaultServlet { * and we can't get any resources (like icons) out of the .war */ @Override - public Resource getResource(String pathInContext) + public File getResource(String pathInContext) { if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") || pathInContext.equals("/index.html") || pathInContext.startsWith("/.icons/")) return super.getResource(pathInContext); // files in the i2psnark/ directory - try { - return _resourceBase.addPath(pathInContext); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } + return new File(_resourceBase, pathInContext); } /** - * Tell the browser to cache the icons + * Handle what we can here, calling super.doGet() for the rest. * @since 0.8.3 */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - super.doGet(request, response); + doGetAndPost(request, response); } /** + * Handle what we can here, calling super.doPost() for the rest. + * @since Jetty 7 + */ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + doGetAndPost(request, response); + } + + /** + * Handle what we can here, calling super.doGet() or super.doPost() for the rest. + * * Some parts modified from: *
       // ========================================================================
@@ -137,14 +151,11 @@ public class I2PSnarkServlet extends DefaultServlet {
      * 
* */ - @Override - public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + private void doGetAndPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Service " + req.getMethod() + " \"" + req.getContextPath() + "\" \"" + req.getServletPath() + "\" \"" + req.getPathInfo() + '"'); // since we are not overriding handle*(), do this here String method = req.getMethod(); - if (!(method.equals("GET") || method.equals("HEAD") || method.equals("POST"))) { - resp.sendError(405); - return; - } _themePath = "/themes/snark/" + _manager.getTheme() + '/'; _imgPath = _themePath + "images/"; // this is the part after /i2psnark @@ -176,17 +187,18 @@ public class I2PSnarkServlet extends DefaultServlet { // index.jsp doesn't work, it is grabbed by the war handler before here if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html") || path.equals("/_post") || isConfigure)) { if (path.endsWith("/")) { + // Listing of a torrent (torrent detail page) // bypass the horrid Resource.getListHTML() String pathInfo = req.getPathInfo(); - String pathInContext = URIUtil.addPaths(path, pathInfo); + String pathInContext = addPaths(path, pathInfo); req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html; charset=UTF-8"); - Resource resource = getResource(pathInContext); - if (resource == null || (!resource.exists())) { + File resource = getResource(pathInContext); + if (resource == null) { resp.sendError(404); } else { - String base = URIUtil.addPaths(req.getRequestURI(), "/"); + String base = addPaths(req.getRequestURI(), "/"); String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null); if (method.equals("POST")) { // P-R-G @@ -198,11 +210,19 @@ public class I2PSnarkServlet extends DefaultServlet { } } } else { - super.service(req, resp); + // local completed files in torrent directories + if (method.equals("GET") || method.equals("HEAD")) + super.doGet(req, resp); + else if (method.equals("POST")) + super.doPost(req, resp); + else + resp.sendError(405); } return; } + // Either the main page or /configure + req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html; charset=UTF-8"); @@ -236,7 +256,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("\n" + "\n"); } @@ -249,18 +269,24 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("
"); List sortedTrackers = null; if (isConfigure) { - out.write("
"); out.write("\"\"  "); - out.write(_("I2PSnark")); + if (_contextName.equals(DEFAULT_NAME)) + out.write(_("I2PSnark")); + else + out.write(_contextName); out.write(""); } else { - out.write("
"); out.write("\"\"  "); - out.write(_("I2PSnark")); + if (_contextName.equals(DEFAULT_NAME)) + out.write(_("I2PSnark")); + else + out.write(_contextName); out.write(" "); out.write(_("Forum")); out.write("\n"); @@ -302,7 +328,7 @@ public class I2PSnarkServlet extends DefaultServlet { List msgs = _manager.getMessages(); if (!msgs.isEmpty()) { out.write("
"); - out.write(" 0) @@ -342,7 +368,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write(_("Status")); out.write("\">\n"); if (_manager.util().connected() && !snarks.isEmpty()) { - out.write(" "); out.write(""); } else if ((!_manager.util().isConnecting()) && !snarks.isEmpty()) { if (isDegraded) - out.write("\n"); - String uri = "/i2psnark/"; + String uri = _contextPath + '/'; boolean showDebug = "2".equals(peerParam); for (int i = 0; i < snarks.size(); i++) { Snark snark = (Snark)snarks.get(i); @@ -1245,7 +1271,7 @@ public class I2PSnarkServlet extends DefaultServlet { } else if (isRunning) { // Stop Button if (isDegraded) - out.write("\n" + + out.write("
\n" + "
\n" + "\n" + "\n" + @@ -1632,7 +1658,8 @@ public class I2PSnarkServlet extends DefaultServlet { out.write(_("Data directory")); out.write(": " + dataDir + " ("); - out.write(_("Edit i2psnark.config and restart to change")); + // translators: parameter is a file name + out.write(_("Edit {0} and restart to change", _manager.getConfigFilename())); out.write(")
\n" + ""); @@ -1802,7 +1829,7 @@ public class I2PSnarkServlet extends DefaultServlet { /** @since 0.9 */ private void writeTrackerForm(PrintWriter out, HttpServletRequest req) throws IOException { StringBuilder buf = new StringBuilder(1024); - buf.append("\n" + + buf.append("\n" + "
\n" + "\n" + "\n" + @@ -2031,7 +2058,7 @@ public class I2PSnarkServlet extends DefaultServlet { * @return String of HTML or null if postParams != null * @since 0.7.14 */ - private String getListHTML(Resource r, String base, boolean parent, Map postParams) + private String getListHTML(File r, String base, boolean parent, Map postParams) throws IOException { String[] ls = null; @@ -2040,9 +2067,10 @@ public class I2PSnarkServlet extends DefaultServlet { Arrays.sort(ls, Collator.getInstance()); } // if r is not a directory, we are only showing torrent info section - String title = URIUtil.decodePath(base); - if (title.startsWith("/i2psnark/")) - title = title.substring("/i2psnark/".length()); + String title = decodePath(base); + String cpath = _contextPath + '/'; + if (title.startsWith(cpath)) + title = title.substring(cpath.length()); // Get the snark associated with this directory String torrentName; @@ -2067,8 +2095,13 @@ public class I2PSnarkServlet extends DefaultServlet { title = _("Torrent") + ": " + title; buf.append(title); buf.append("").append(HEADER_A).append(_themePath).append(HEADER_B).append("" + - "\n
\n"); + "\n
\n"); if (parent) // always true buf.append("
"); @@ -2089,7 +2122,7 @@ public class I2PSnarkServlet extends DefaultServlet { buf.append("") .append("\"\" ") .append(_("Torrent file")) - .append(": ") + .append(": ") .append(fullPath) .append("\n"); @@ -2228,7 +2261,7 @@ public class I2PSnarkServlet extends DefaultServlet { .append("\">\n"); buf.append("\n\n"); buf.append("\"\" ") .append(_("Up to higher level directory")) .append("\n"); @@ -2239,12 +2272,12 @@ public class I2PSnarkServlet extends DefaultServlet { boolean showSaveButton = false; for (int i=0 ; i< ls.length ; i++) { - String encoded=URIUtil.encodePath(ls[i]); + String encoded = encodePath(ls[i]); // bugfix for I2P - Backport from Jetty 6 (zero file lengths and last-modified times) // http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs // See resource.diff attachment //Resource item = addPath(encoded); - Resource item = r.addPath(ls[i]); + File item = new File(r, ls[i]); String rowClass = (i % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd"); buf.append(""); @@ -2264,7 +2297,7 @@ public class I2PSnarkServlet extends DefaultServlet { } else { Storage storage = snark.getStorage(); try { - File f = item.getFile(); + File f = item; if (f != null) { long remaining = storage.remaining(f.getCanonicalPath()); if (remaining < 0) { @@ -2294,9 +2327,9 @@ public class I2PSnarkServlet extends DefaultServlet { } } - String path=URIUtil.addPaths(base,encoded); + String path=addPaths(base,encoded); if (item.isDirectory() && !path.endsWith("/")) - path=URIUtil.addPaths(path,"/"); + path=addPaths(path,"/"); String icon = toIcon(item); buf.append(""); if (showPriority) { buf.append(""); - File f = item.getFile(); + File f = item; if ((!complete) && (!item.isDirectory()) && f != null) { int pri = snark.getStorage().getPriority(f.getCanonicalPath()); buf.append(""; + private String toImg(String icon) { + return "\"\""; } /** @since 0.8.2 */ - private static String toImg(String icon, String altText) { - return "\"""; + private String toImg(String icon, String altText) { + return "\"""; } /** @since 0.8.1 */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java b/apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java new file mode 100644 index 0000000000..e24764ff5e --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java @@ -0,0 +1,131 @@ +// ======================================================================== +// Copyright 2000-2005 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ======================================================================== + +package org.klomp.snark.web; + +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.ServletContext; + + +/* ------------------------------------------------------------ */ +/** + * Based on MimeTypes from Jetty 6.1.26, heavily simplified + * and modified to remove all dependencies on Jetty libs. + * + * Supports mime types only, not encodings. + * Does not support a default "*" mapping. + * + * This is only for local mappings. + * Caller should use getServletContext().getMimeType() if this returns null. + * + * + * ------------------------------------------------------------ + * + * @author Greg Wilkins + * + * @since Jetty 7 + */ +class MimeTypes +{ + + private final Map _mimeMap; + + public MimeTypes() { + _mimeMap = new ConcurrentHashMap(); + } + + /* ------------------------------------------------------------ */ + /** + * @param resourcePath A Map of file extension to mime-type. + */ + public void loadMimeMap(String resourcePath) { + loadMimeMap(_mimeMap, resourcePath); + } + + /** + * Tries both webapp and system class loader, since Jetty blocks + * its classes from the webapp class loader. + */ + private static void loadMimeMap(Map map, String resourcePath) { + try + { + ResourceBundle mime; + try { + mime = ResourceBundle.getBundle(resourcePath); + } catch(MissingResourceException e) { + // Jetty 7 webapp classloader blocks jetty classes + // http://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading + //System.out.println("No mime types loaded from " + resourcePath + ", trying system classloader"); + mime = ResourceBundle.getBundle(resourcePath, Locale.getDefault(), ClassLoader.getSystemClassLoader()); + } + Enumeration i = mime.getKeys(); + while(i.hasMoreElements()) + { + String ext = i.nextElement(); + String m = mime.getString(ext); + map.put(ext.toLowerCase(Locale.US), m); + } + //System.out.println("Loaded " + map.size() + " mime types from " + resourcePath); + } catch(MissingResourceException e) { + //System.out.println("No mime types loaded from " + resourcePath); + } + } + + /* ------------------------------------------------------------ */ + /** Get the MIME type by filename extension. + * + * Returns ONLY local mappings. + * Caller should use getServletContext().getMimeType() if this returns null. + * + * @param filename A file name + * @return MIME type matching the longest dot extension of the + * file name. + */ + public String getMimeByExtension(String filename) + { + String type=null; + + if (filename!=null) + { + int i=-1; + while(type==null) + { + i=filename.indexOf(".",i+1); + + if (i<0 || i>=filename.length()) + break; + + String ext=filename.substring(i+1).toLowerCase(Locale.US); + type = _mimeMap.get(ext); + } + } + return type; + } + + /* ------------------------------------------------------------ */ + /** Set a mime mapping + * @param extension + * @param type + */ + public void addMimeMapping(String extension, String type) + { + _mimeMap.put(extension.toLowerCase(Locale.US), type); + } +} diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/RunStandalone.java b/apps/i2psnark/java/src/org/klomp/snark/web/RunStandalone.java index 543fc8a7ee..30700dd4d7 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/RunStandalone.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/RunStandalone.java @@ -5,7 +5,7 @@ import java.io.File; import net.i2p.I2PAppContext; import net.i2p.util.FileUtil; -import org.mortbay.jetty.Server; +import org.eclipse.jetty.server.Server; public class RunStandalone { static { diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/WriterOutputStream.java b/apps/i2psnark/java/src/org/klomp/snark/web/WriterOutputStream.java new file mode 100644 index 0000000000..c4aa37bc54 --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/web/WriterOutputStream.java @@ -0,0 +1,19 @@ +package org.klomp.snark.web; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + +/** + * Treat a writer as an output stream. Quick 'n dirty, none + * of that "intarnasheeonaleyzayshun" stuff. So we can treat + * the jsp's PrintWriter as an OutputStream + * + * @since Jetty 7 copied from routerconsole + */ +class WriterOutputStream extends OutputStream { + private final Writer _writer; + + public WriterOutputStream(Writer writer) { _writer = writer; } + public void write(int b) throws IOException { _writer.write(b); } +} diff --git a/apps/i2psnark/mime.properties b/apps/i2psnark/mime.properties new file mode 100644 index 0000000000..4fc8f7a5e4 --- /dev/null +++ b/apps/i2psnark/mime.properties @@ -0,0 +1,17 @@ +7z = application/x-7z-compressed +ape = audio/ape +bz2 = application/x-bzip2 +flac = audio/ogg +flv = video/x-flv +m4a = audio/mp4a-latm +m4v = video/x-m4v +mkv = video/x-matroska +mp4 = video/mp4 +nfo = text/plain +ogm = video/ogg +ogv = video/ogg +oga = audio/ogg +rar = application/x-rar-compressed +war = application/java-archive +wma = audio/x-ms-wma +wmv = video/x-ms-wmv diff --git a/apps/jetty/README-i2p.txt b/apps/jetty/README-i2p.txt new file mode 100644 index 0000000000..3aa81e1ec1 --- /dev/null +++ b/apps/jetty/README-i2p.txt @@ -0,0 +1,3 @@ +jetty-distribution-xxx is only what we need out of jetty-distribution-xxx.zip. + +NOTICE and LICENSE files moved to ../../../licenses diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml index 04f988b90d..b0f72eb0f6 100644 --- a/apps/jetty/build.xml +++ b/apps/jetty/build.xml @@ -1,11 +1,11 @@ - - - + + + - + @@ -89,20 +89,40 @@ Reasons for inclusion: start.jar: Needed for clients.config startup of eepsites jetty-util-xxx.jar: LifeCycle (base class for stuff), URIUtil (used in i2psnark) - jetty-sslengine-xxx.jar: SSL NIO Connector for console - jetty-java5-threadpool-xxx.jar: Concurrent thread pool for eepsite + jetty-deploy, -http, -io, -security, -servlet, -webapp: All split out from main server jar in Jetty 7 + jetty-continuation-xxx.jar: Needed? Useful? + jetty-servlets-xxx.jar: Needed for CGI for eepsite + jetty-sslengine-xxx.jar: Old Jetty 6, now a dummy + jetty-java5-threadpool-xxx.jar: Old Jetty 6, now a dummy glassfish 2.1: Not used, too old, see Tomcat below. jetty-rewrite-handler: Not used by I2P, but only 20KB and could be useful for eepsites jetty-management: Not used by I2P, but only 34KB and could be useful for eepsites, and we bundled it with Jetty 5 All of these are available in the Ubuntu packages libjetty-java and libjetty-extra-java --> - + + + + + + + + + + - - - - + + + + + + + + + + + + - + - + @@ -1153,13 +1153,13 @@ - + - + diff --git a/installer/resources/clients.config b/installer/resources/clients.config index 8dc6573381..cd68fb5c04 100644 --- a/installer/resources/clients.config +++ b/installer/resources/clients.config @@ -40,7 +40,7 @@ clientApp.2.args=i2ptunnel.config clientApp.2.startOnLoad=true # run our own eepsite with a seperate jetty instance -clientApp.3.main=org.mortbay.start.Main +clientApp.3.main=net.i2p.jetty.JettyStart clientApp.3.name=I2P webserver (eepsite) ## To use the rewrite handler, edit jetty-rewrite.xml and use: #clientApp.3.args="/path/to/jetty.xml" "/path/to/jetty-rewrite.xml" diff --git a/installer/resources/eepsite/contexts/base-context.xml b/installer/resources/eepsite/contexts/base-context.xml index 4eaf2daea3..36ca36784b 100644 --- a/installer/resources/eepsite/contexts/base-context.xml +++ b/installer/resources/eepsite/contexts/base-context.xml @@ -1,29 +1,23 @@ - + - + / ./eepsite/docroot/ - - - - - org.mortbay.jetty.servlet.Default.cacheControl - max-age=3600,public - - - + + cacheControl + max-age=3600,public - + sud application/zip @@ -40,7 +34,7 @@ to serve static html files and images. - org.mortbay.jetty.servlet.DefaultServlet + org.eclipse.jetty.servlet.DefaultServlet / diff --git a/installer/resources/eepsite/contexts/cgi-context.xml b/installer/resources/eepsite/contexts/cgi-context.xml index 4a0b0b6d21..3ae8f390d1 100644 --- a/installer/resources/eepsite/contexts/cgi-context.xml +++ b/installer/resources/eepsite/contexts/cgi-context.xml @@ -1,5 +1,5 @@ - + - + /cgi-bin ./eepsite/cgi-bin/ - - - - - Path - /usr/local/bin:/bin:/usr/bin - - - + + Path + /usr/local/bin:/bin:/usr/bin - org.mortbay.servlet.CGI + org.eclipse.jetty.servlets.CGI / diff --git a/installer/resources/eepsite/etc/webdefault.xml b/installer/resources/eepsite/etc/webdefault.xml index e13916f101..d0a6f4bf67 100644 --- a/installer/resources/eepsite/etc/webdefault.xml +++ b/installer/resources/eepsite/etc/webdefault.xml @@ -35,23 +35,23 @@ - org.mortbay.jetty.webapp.NoTLDJarPattern + org.eclipse.jetty.webapp.NoTLDJarPattern start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar @@ -112,7 +112,7 @@ default - org.mortbay.jetty.servlet.DefaultServlet + org.eclipse.jetty.servlet.DefaultServlet acceptRanges true @@ -306,7 +306,7 @@ + + + + + + - - - - - - - - + + + + + + + + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - --> - + + + true + + + + + - - service:jmx:rmi://localhost:2100/jndi/rmi://localhost:2099/jmxrmi - + - - - - --> + + + + + + + + diff --git a/installer/resources/eepsite/jetty-rewrite.xml b/installer/resources/eepsite/jetty-rewrite.xml index ef5043aac0..1d208f8bfc 100644 --- a/installer/resources/eepsite/jetty-rewrite.xml +++ b/installer/resources/eepsite/jetty-rewrite.xml @@ -1,149 +1,117 @@ - + - + - + - - + true false requestedPath - - + + + + + + - - - - - + + + + /favicon.ico + Cache-Control + Max-Age=3600,public + true + + + - - - - - - /* - 500 - Server busy - - - - - + + + + + /rewrite/ + /rewrite/info.html + + + - - - - + + + + + /some/old/context + /rewritten/newcontext + + + - - - - /favicon.ico - Cache-Control - Max-Age=3600,public - true - - + + + + + /rewrite/for/* + /rewritten/ + + + + + + + + + (.*?)/reverse/([^/]*)/(.*) + $1/reverse/$3/$2 + + + + + + + + /* + visited + yes + + + + + + + + + /redirect/* + /redirected + + + - - - - /rewrite/dump/regex/([^/]*)/(.*) - /test/dump/$2/$1 - - + + + + + /400Error + 400 + ResponsePatternRule Demo + + + - - - - /rewrite - /rewrittento - - - - - - - /rewrite/session/ - 401 - Setting error code 401 - - - - - - - *.jsp - Server - Server for JSP - - - - - - - /rewrite/dispatch - http://jetty.mortbay.org - - - - - - X-Forwarded-Scheme - https - https - - - - - - - - - mortbay.com - www.mortbay.com - mortbay.org - www.mortbay.org - - - - - - - /* - CookiePatternRule - 1 - - - - - - - - - + diff --git a/installer/resources/eepsite/jetty-ssl.xml b/installer/resources/eepsite/jetty-ssl.xml index 787635f563..f86c025070 100644 --- a/installer/resources/eepsite/jetty-ssl.xml +++ b/installer/resources/eepsite/jetty-ssl.xml @@ -1,35 +1,35 @@ - + - + + + - - + + + + + ./eepsite/etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + OBF:1u2u1wml1z7s1z7a1wnl1u2g + ./eepsite/etc/keystore + OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + - + + 8443 30000 false - 2000 - ./eepsite/etc/keystore.ks - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - OBF:1u2u1wml1z7s1z7a1wnl1u2g - ./eepsite/etc/keystore.ks - OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 - 2000 - + 2 + 100 diff --git a/installer/resources/eepsite/jetty.xml b/installer/resources/eepsite/jetty.xml index 0e873ca770..7a57fa3fc0 100644 --- a/installer/resources/eepsite/jetty.xml +++ b/installer/resources/eepsite/jetty.xml @@ -1,5 +1,5 @@ - + @@ -18,8 +18,8 @@ - - + + @@ -44,7 +44,7 @@ - + @@ -57,7 +57,7 @@ Requests above the max will be queued --> - - 0 - 1 - 24 + + + 3 + + 16 + + 60000 @@ -97,7 +100,7 @@ --> - + 127.0.0.1 7658 60000 @@ -106,7 +109,7 @@ 8443 5000 5000 - false + false @@ -117,7 +120,7 @@ - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern + .*/.*jsp-api-[^/]*\.jar$|.*/.*jsp-[^/]*\.jar$|.*/.*taglibs[^/]*\.jar$ + + + + + @@ -193,16 +221,16 @@ - - - - - ./eepsite/contexts - - 0 - - - + + + + + ./eepsite/contexts + 30 + + + + @@ -219,7 +247,7 @@ - + ./eepsite/webapps false @@ -231,6 +259,20 @@ + + @@ -240,9 +282,9 @@ + + + +
+ +

Eclipse Public License - v 1.0 +

+ +

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER +THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, +REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT.

+ +

1. DEFINITIONS

+ +

"Contribution" means:

+ +

a) +in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and
+b) in the case of each subsequent Contributor:

+ +

i) +changes to the Program, and

+ +

ii) +additions to the Program;

+ +

where +such changes and/or additions to the Program originate from and are distributed +by that particular Contributor. A Contribution 'originates' from a Contributor +if it was added to the Program by such Contributor itself or anyone acting on +such Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) are not derivative +works of the Program.

+ +

"Contributor" means any person or +entity that distributes the Program.

+ +

"Licensed Patents " mean patent +claims licensable by a Contributor which are necessarily infringed by the use +or sale of its Contribution alone or when combined with the Program.

+ +

"Program" means the Contributions +distributed in accordance with this Agreement.

+ +

"Recipient" means anyone who +receives the Program under this Agreement, including all Contributors.

+ +

2. GRANT OF RIGHTS

+ +

a) +Subject to the terms of this Agreement, each Contributor hereby grants Recipient +a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly +display, publicly perform, distribute and sublicense the Contribution of such +Contributor, if any, and such derivative works, in source code and object code +form.

+ +

b) +Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free +patent license under Licensed Patents to make, use, sell, offer to sell, import +and otherwise transfer the Contribution of such Contributor, if any, in source +code and object code form. This patent license shall apply to the combination +of the Contribution and the Program if, at the time the Contribution is added +by the Contributor, such addition of the Contribution causes such combination +to be covered by the Licensed Patents. The patent license shall not apply to +any other combinations which include the Contribution. No hardware per se is +licensed hereunder.

+ +

c) +Recipient understands that although each Contributor grants the licenses to its +Contributions set forth herein, no assurances are provided by any Contributor +that the Program does not infringe the patent or other intellectual property +rights of any other entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement of intellectual +property rights or otherwise. As a condition to exercising the rights and +licenses granted hereunder, each Recipient hereby assumes sole responsibility +to secure any other intellectual property rights needed, if any. For example, +if a third party patent license is required to allow Recipient to distribute +the Program, it is Recipient's responsibility to acquire that license before +distributing the Program.

+ +

d) +Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in +this Agreement.

+ +

3. REQUIREMENTS

+ +

A Contributor may choose to distribute the +Program in object code form under its own license agreement, provided that: +

+ +

a) +it complies with the terms and conditions of this Agreement; and

+ +

b) +its license agreement:

+ +

i) +effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose;

+ +

ii) +effectively excludes on behalf of all Contributors all liability for damages, +including direct, indirect, special, incidental and consequential damages, such +as lost profits;

+ +

iii) +states that any provisions which differ from this Agreement are offered by that +Contributor alone and not by any other party; and

+ +

iv) +states that source code for the Program is available from such Contributor, and +informs licensees how to obtain it in a reasonable manner on or through a +medium customarily used for software exchange.

+ +

When the Program is made available in source +code form:

+ +

a) +it must be made available under this Agreement; and

+ +

b) a +copy of this Agreement must be included with each copy of the Program.

+ +

Contributors may not remove or alter any +copyright notices contained within the Program.

+ +

Each Contributor must identify itself as the +originator of its Contribution, if any, in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution.

+ +

4. COMMERCIAL DISTRIBUTION

+ +

Commercial distributors of software may +accept certain responsibilities with respect to end users, business partners +and the like. While this license is intended to facilitate the commercial use +of the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes the +Program in a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified Contributor to +the extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may participate +in any such claim at its own expense.

+ +

For example, a Contributor might include the +Program in a commercial product offering, Product X. That Contributor is then a +Commercial Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance claims and +warranties are such Commercial Contributor's responsibility alone. Under this +section, the Commercial Contributor would have to defend claims against the +other Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages.

+ +

5. NO WARRANTY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, +WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and distributing the +Program and assumes all risks associated with its exercise of rights under this +Agreement , including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations.

+ +

6. DISCLAIMER OF LIABILITY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF +THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES.

+ +

7. GENERAL

+ +

If any provision of this Agreement is invalid +or unenforceable under applicable law, it shall not affect the validity or +enforceability of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable.

+ +

If Recipient institutes patent litigation +against any entity (including a cross-claim or counterclaim in a lawsuit) +alleging that the Program itself (excluding combinations of the Program with +other software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the date +such litigation is filed.

+ +

All Recipient's rights under this Agreement +shall terminate if it fails to comply with any of the material terms or +conditions of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive.

+ +

Everyone is permitted to copy and distribute +copies of this Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. The Eclipse Foundation is the initial +Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved.

+ +

This Agreement is governed by the laws of the +State of New York and the intellectual property laws of the United States of +America. No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation.

+ +

 

+ +
+ + \ No newline at end of file diff --git a/licenses/NOTICE-Jetty.html b/licenses/NOTICE-Jetty.html new file mode 100644 index 0000000000..e9461a8e86 --- /dev/null +++ b/licenses/NOTICE-Jetty.html @@ -0,0 +1,111 @@ + + + + + +Eclipse.org Software User Agreement + +

Eclipse Foundation Software User Agreement

+

March 17, 2005

+ +

Usage Of Content

+ +

THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.

+ +

Applicable Licenses

+ +

Unless otherwise indicated, all Content made available by the +Eclipse Foundation is provided to you under the terms and conditions of +the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is +provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

+ +

Content includes, but is not limited to, source code, object code, +documentation and other files maintained in the Eclipse.org CVS +repository ("Repository") in CVS modules ("Modules") and made available +as downloadable archives ("Downloads").

+ +
    +
  • Content may be structured and packaged into modules to +facilitate delivering, extending, and upgrading the Content. Typical +modules may include plug-ins ("Plug-ins"), plug-in fragments +("Fragments"), and features ("Features").
  • +
  • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins".
  • +
  • A +Feature is a bundle of one or more Plug-ins and/or Fragments and +associated material. Each Feature may be packaged as a sub-directory in +a directory named "features". Within a Feature, files named +"feature.xml" may contain a list of the names and version numbers of +the Plug-ins and/or Fragments associated with that Feature.
  • +
  • Features +may also include other Features ("Included Features"). Within a +Feature, files named "feature.xml" may contain a list of the names and +version numbers of Included Features.
  • +
+ +

The terms and conditions governing Plug-ins and Fragments should be +contained in files named "about.html" ("Abouts"). The terms and +conditions governing Features and +Included Features should be contained in files named "license.html" +("Feature Licenses"). Abouts and Feature Licenses may be located in any +directory of a Download or Module +including, but not limited to the following locations:

+ +
    +
  • The top-level (root) directory
  • +
  • Plug-in and Fragment directories
  • +
  • Inside Plug-ins and Fragments packaged as JARs
  • +
  • Sub-directories of the directory named "src" of certain Plug-ins
  • +
  • Feature directories
  • +
+ +

Note: if a Feature made available by the Eclipse Foundation is +installed using the Eclipse Update Manager, you must agree to a license +("Feature Update License") during the +installation process. If the Feature contains Included Features, the +Feature Update License should either provide you with the terms and +conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be +found in the "license" property of files named "feature.properties" +found within a Feature. +Such Abouts, Feature Licenses, and Feature Update Licenses contain the +terms and conditions (or references to such terms and conditions) that +govern your use of the associated Content in +that directory.

+ +

THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER +TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND +CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

+ + + +

IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND +CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, +or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions +govern that particular Content.

+ +

Cryptography

+ +

Content may contain encryption software. The country in which you +are currently may have restrictions on the import, possession, and use, +and/or re-export to another country, of encryption software. BEFORE +using any encryption software, please check the country's laws, +regulations and policies concerning the import, possession, or use, and +re-export of encryption software, to see if this is permitted.

+ +Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. + \ No newline at end of file diff --git a/licenses/NOTICE-Jetty.txt b/licenses/NOTICE-Jetty.txt deleted file mode 100644 index 21d4ed3d0e..0000000000 --- a/licenses/NOTICE-Jetty.txt +++ /dev/null @@ -1,36 +0,0 @@ -============================================================== - Jetty Web Container - Copyright 1995-2009 Mort Bay Consulting Pty Ltd -============================================================== - -The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd -unless otherwise noted. It is licensed under the apache 2.0 -license. - -The javax.servlet package used by Jetty is copyright -Sun Microsystems, Inc and Apache Software Foundation. It is -distributed under the Common Development and Distribution License. -You can obtain a copy of the license at -https://glassfish.dev.java.net/public/CDDLv1.0.html. - -The UnixCrypt.java code ~Implements the one way cryptography used by -Unix systems for simple password protection. Copyright 1996 Aki Yoshida, -modified April 2001 by Iris Van den Broeke, Daniel Deville. -Permission to use, copy, modify and distribute UnixCrypt -for non-commercial or commercial purposes and without fee is -granted provided that the copyright notice appears in all copies. - -The default JSP implementation is provided by the Glassfish JSP engine -from project Glassfish http://glassfish.dev.java.net. Copyright 2005 -Sun Microsystems, Inc. and portions Copyright Apache Software Foundation. - -Some portions of the code are Copyright: - 2006 Tim Vernum - 1999 Jason Gilbert. - -The jboss integration module contains some LGPL code. - -The win32 Java Service Wrapper (v3.2.3) is Copyright (c) 1999, 2006 -Tanuki Software, Inc. and 2001 Silver Egg Technology. It is -covered by an open license which is viewable at -http://svn.codehaus.org/jetty/jetty/branches/jetty-6.1/extras/win32service/LICENSE.txt diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 725ca2779b..125d2e075c 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -128,7 +128,8 @@ public class Router implements RouterClock.ClockShiftListener { // Fixed in Jetty 5.1.15 but we are running 5.1.12 // The default is true, unfortunately it was previously // set to false in wrapper.config thru 0.7.10 so we must set it back here. - System.setProperty("org.mortbay.util.FileResource.checkAliases", "true"); + // Not in Jetty 7 + //System.setProperty("org.mortbay.util.FileResource.checkAliases", "true"); } /** diff --git a/router/java/src/net/i2p/router/startup/MigrateJetty.java b/router/java/src/net/i2p/router/startup/MigrateJetty.java index b78331284c..3b197f9de6 100644 --- a/router/java/src/net/i2p/router/startup/MigrateJetty.java +++ b/router/java/src/net/i2p/router/startup/MigrateJetty.java @@ -35,13 +35,15 @@ import net.i2p.router.RouterContext; */ abstract class MigrateJetty { private static boolean _wasChecked; - private static boolean _hasJetty6; + private static boolean _hasLatestJetty; private static final String OLD_CLASS = "org.mortbay.jetty.Server"; - private static final String NEW_CLASS = "org.mortbay.start.Main"; - private static final String BACKUP = "jetty5.xml"; - private static final String JETTY6_TEMPLATE_DIR = "eepsite-jetty6"; - private static final String JETTY6_TEMPLATE_PKGDIR = "eepsite"; + private static final String OLD_CLASS_6 = "org.mortbay.start.Main"; + private static final String NEW_CLASS = "net.i2p.jetty.JettyStart"; + private static final String TEST_CLASS = "org.eclipse.jetty.server.Server"; + private static final String BACKUP = "jetty6.xml"; + private static final String JETTY_TEMPLATE_DIR = "eepsite-jetty7"; + private static final String JETTY_TEMPLATE_PKGDIR = "eepsite"; private static final String BASE_CONTEXT = "contexts/base-context.xml"; private static final String CGI_CONTEXT = "contexts/cgi-context.xml"; @@ -49,13 +51,13 @@ abstract class MigrateJetty { boolean shouldSave = false; for (int i = 0; i < apps.size(); i++) { ClientAppConfig app = apps.get(i); - if (!app.className.equals(OLD_CLASS)) + if (!(app.className.equals(OLD_CLASS) || app.className.equals(OLD_CLASS_6))) continue; String client = "client application " + i + " [" + app.clientName + - "] from Jetty 5 " + OLD_CLASS + - " to Jetty 6 " + NEW_CLASS; - if (!hasJetty6()) { - System.err.println("WARNING: Jetty 6 unavailable, cannot migrate " + client); + "] from Jetty 5/6 " + app.className + + " to Jetty 7 " + NEW_CLASS; + if (!hasLatestJetty()) { + System.err.println("WARNING: Jetty 7 unavailable, cannot migrate " + client); continue; } if (app.args == null) @@ -83,10 +85,10 @@ abstract class MigrateJetty { ", cannot migrate " + client); continue; } - File baseEep = new File(ctx.getBaseDir(), JETTY6_TEMPLATE_DIR); + File baseEep = new File(ctx.getBaseDir(), JETTY_TEMPLATE_DIR); // in packages, or perhaps on an uninstall/reinstall, the files are in eepsite/ if (!baseEep.exists()) - baseEep = new File(ctx.getBaseDir(), JETTY6_TEMPLATE_PKGDIR); + baseEep = new File(ctx.getBaseDir(), JETTY_TEMPLATE_PKGDIR); if (baseEep.equals(eepsite)) { // non-split directory yet not an upgrade? shouldn't happen System.err.println("Eepsite in non-split directory " + eepsite + @@ -132,28 +134,29 @@ abstract class MigrateJetty { } if (shouldSave) { File cfgFile = ClientAppConfig.configFile(ctx); - File backup = new File(cfgFile.getAbsolutePath() + ".jetty5"); + File backup = new File(cfgFile.getAbsolutePath() + ".jetty6"); if (backup.exists()) backup = new File(cfgFile.getAbsolutePath() + ctx.random().nextInt()); boolean ok = WorkingDir.copyFile(cfgFile, backup); if (ok) { ClientAppConfig.writeClientAppConfig(ctx, apps); System.err.println("WARNING: Migrated clients config file " + cfgFile + - " from Jetty 5 " + OLD_CLASS + - " to Jetty 6 " + NEW_CLASS + "\n" + + " from Jetty 5/6 " + OLD_CLASS + '/' + OLD_CLASS_6 + + " to Jetty 7 " + NEW_CLASS + "\n" + "Your old clients config file was saved as " + backup); } } } - private static boolean hasJetty6() { + /** do we have Jetty 7? */ + private static boolean hasLatestJetty() { if (!_wasChecked) { try { - LoadClientAppsJob.testClient(NEW_CLASS, null); - _hasJetty6 = true; + LoadClientAppsJob.testClient(TEST_CLASS, null); + _hasLatestJetty = true; } catch (ClassNotFoundException cnfe) {} _wasChecked = true; } - return _hasJetty6; + return _hasLatestJetty; } }