forked from I2P_Developers/i2p.i2p
* Eliminate Jetty dependencies in i2psnark for good.
Required due to webapp classloader changes in Jetty 7, we can no longer access or extend Jetty classes. - Extend javax HttpServlet instead of Jetty's DefaultServlet - Implement BasicServlet to replace functions of DefaultServlet - Add MimeTypes implementation to add to servlet's defaults - Add local mime.properties file, remove checks in I2PSnarkServlet for those we were missing - Eliminate all use of Jetty utility classes including MimeType, Resource, Buffer, Cache, URIUtil, ... TODO: - Use servlet path everywhere, so the war can be renamed - Don't override service()
This commit is contained in:
@ -104,9 +104,11 @@
|
|||||||
<copy todir="build/icons/.icons" >
|
<copy todir="build/icons/.icons" >
|
||||||
<fileset dir="../icons/" />
|
<fileset dir="../icons/" />
|
||||||
</copy>
|
</copy>
|
||||||
|
<!-- mime.properties must be in with the classes -->
|
||||||
|
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
|
||||||
<war destfile="../i2psnark.war" webxml="../web.xml" >
|
<war destfile="../i2psnark.war" webxml="../web.xml" >
|
||||||
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
|
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
|
||||||
<classes dir="./build/obj" includes="**/web/*.class" />
|
<classes dir="./build/obj" includes="**/web/*" />
|
||||||
<fileset dir="build/icons/" />
|
<fileset dir="build/icons/" />
|
||||||
<manifest>
|
<manifest>
|
||||||
<attribute name="Implementation-Version" value="${full.version}" />
|
<attribute name="Implementation-Version" value="${full.version}" />
|
||||||
|
547
apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java
Normal file
547
apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
// ========================================================================
|
||||||
|
// 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. :
|
||||||
|
* <PRE>
|
||||||
|
*
|
||||||
|
* resourceBase Set to replace the context resource base
|
||||||
|
|
||||||
|
* warBase Path allowed for resource in war
|
||||||
|
*
|
||||||
|
* </PRE>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @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<Integer.MAX_VALUE)
|
||||||
|
response.setContentLength((int)count);
|
||||||
|
else
|
||||||
|
response.setHeader("Content-Length", Long.toString(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
long ct = content.getCacheTime();
|
||||||
|
if (ct>=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()
|
||||||
|
{
|
||||||
|
return (new File(_context.getBaseDir(), "webapps/i2psnark.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) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@ -44,31 +45,28 @@ import org.klomp.snark.Tracker;
|
|||||||
import org.klomp.snark.TrackerClient;
|
import org.klomp.snark.TrackerClient;
|
||||||
import org.klomp.snark.dht.DHT;
|
import org.klomp.snark.dht.DHT;
|
||||||
|
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We extend Default instead of HTTPServlet so we can handle
|
* Refactored to eliminate Jetty dependencies
|
||||||
* i2psnark/ file requests with http:// instead of the flaky and
|
|
||||||
* often-blocked-by-the-browser file://
|
|
||||||
*/
|
*/
|
||||||
public class I2PSnarkServlet extends DefaultServlet {
|
public class I2PSnarkServlet extends BasicServlet {
|
||||||
private I2PAppContext _context;
|
/** generally "/i2psnark" */
|
||||||
private Log _log;
|
private String _contextPath;
|
||||||
private SnarkManager _manager;
|
private SnarkManager _manager;
|
||||||
private static long _nonce;
|
private static long _nonce;
|
||||||
private Resource _resourceBase;
|
|
||||||
private String _themePath;
|
private String _themePath;
|
||||||
private String _imgPath;
|
private String _imgPath;
|
||||||
private String _lastAnnounceURL;
|
private String _lastAnnounceURL;
|
||||||
|
|
||||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||||
|
|
||||||
|
public I2PSnarkServlet() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ServletConfig cfg) throws ServletException {
|
public void init(ServletConfig cfg) throws ServletException {
|
||||||
_context = I2PAppContext.getGlobalContext();
|
super.init(cfg);
|
||||||
_log = _context.logManager().getLog(I2PSnarkServlet.class);
|
_contextPath = getServletContext().getContextPath();
|
||||||
_nonce = _context.random().nextLong();
|
_nonce = _context.random().nextLong();
|
||||||
_manager = new SnarkManager(_context);
|
_manager = new SnarkManager(_context);
|
||||||
String configFile = _context.getProperty(PROP_CONFIG_FILE);
|
String configFile = _context.getProperty(PROP_CONFIG_FILE);
|
||||||
@ -76,10 +74,9 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
configFile = "i2psnark.config";
|
configFile = "i2psnark.config";
|
||||||
_manager.loadConfig(configFile);
|
_manager.loadConfig(configFile);
|
||||||
_manager.start();
|
_manager.start();
|
||||||
try {
|
loadMimeMap("org/klomp/snark/web/mime");
|
||||||
_resourceBase = Resource.newResource(_manager.getDataDir().getAbsolutePath());
|
setResourceBase(_manager.getDataDir());
|
||||||
} catch (IOException ioe) {}
|
setWarBase("/.icons/");
|
||||||
super.init(cfg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,17 +92,13 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
* and we can't get any resources (like icons) out of the .war
|
* and we can't get any resources (like icons) out of the .war
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Resource getResource(String pathInContext)
|
public File getResource(String pathInContext)
|
||||||
{
|
{
|
||||||
if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") ||
|
if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") ||
|
||||||
pathInContext.equals("/index.html") || pathInContext.startsWith("/.icons/"))
|
pathInContext.equals("/index.html") || pathInContext.startsWith("/.icons/"))
|
||||||
return super.getResource(pathInContext);
|
return super.getResource(pathInContext);
|
||||||
// files in the i2psnark/ directory
|
// files in the i2psnark/ directory
|
||||||
try {
|
return new File(_resourceBase, pathInContext);
|
||||||
return _resourceBase.addPath(pathInContext);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,6 +132,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
//_log.error("Service " + req.getMethod() + " \"" + req.getContextPath() + "\" \"" + req.getServletPath() + "\" \"" + req.getPathInfo() + '"');
|
||||||
// since we are not overriding handle*(), do this here
|
// since we are not overriding handle*(), do this here
|
||||||
String method = req.getMethod();
|
String method = req.getMethod();
|
||||||
if (!(method.equals("GET") || method.equals("HEAD") || method.equals("POST"))) {
|
if (!(method.equals("GET") || method.equals("HEAD") || method.equals("POST"))) {
|
||||||
@ -178,15 +172,15 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
if (path.endsWith("/")) {
|
if (path.endsWith("/")) {
|
||||||
// bypass the horrid Resource.getListHTML()
|
// bypass the horrid Resource.getListHTML()
|
||||||
String pathInfo = req.getPathInfo();
|
String pathInfo = req.getPathInfo();
|
||||||
String pathInContext = URIUtil.addPaths(path, pathInfo);
|
String pathInContext = addPaths(path, pathInfo);
|
||||||
req.setCharacterEncoding("UTF-8");
|
req.setCharacterEncoding("UTF-8");
|
||||||
resp.setCharacterEncoding("UTF-8");
|
resp.setCharacterEncoding("UTF-8");
|
||||||
resp.setContentType("text/html; charset=UTF-8");
|
resp.setContentType("text/html; charset=UTF-8");
|
||||||
Resource resource = getResource(pathInContext);
|
File resource = getResource(pathInContext);
|
||||||
if (resource == null || (!resource.exists())) {
|
if (resource == null) {
|
||||||
resp.sendError(404);
|
resp.sendError(404);
|
||||||
} else {
|
} else {
|
||||||
String base = URIUtil.addPaths(req.getRequestURI(), "/");
|
String base = addPaths(req.getRequestURI(), "/");
|
||||||
String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null);
|
String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null);
|
||||||
if (method.equals("POST")) {
|
if (method.equals("POST")) {
|
||||||
// P-R-G
|
// P-R-G
|
||||||
@ -2031,7 +2025,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
* @return String of HTML or null if postParams != null
|
* @return String of HTML or null if postParams != null
|
||||||
* @since 0.7.14
|
* @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
|
throws IOException
|
||||||
{
|
{
|
||||||
String[] ls = null;
|
String[] ls = null;
|
||||||
@ -2040,7 +2034,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
Arrays.sort(ls, Collator.getInstance());
|
Arrays.sort(ls, Collator.getInstance());
|
||||||
} // if r is not a directory, we are only showing torrent info section
|
} // if r is not a directory, we are only showing torrent info section
|
||||||
|
|
||||||
String title = URIUtil.decodePath(base);
|
String title = decodePath(base);
|
||||||
if (title.startsWith("/i2psnark/"))
|
if (title.startsWith("/i2psnark/"))
|
||||||
title = title.substring("/i2psnark/".length());
|
title = title.substring("/i2psnark/".length());
|
||||||
|
|
||||||
@ -2228,7 +2222,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
.append("\"></th>\n");
|
.append("\"></th>\n");
|
||||||
buf.append("</tr>\n</thead>\n");
|
buf.append("</tr>\n</thead>\n");
|
||||||
buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
|
buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
|
||||||
buf.append(URIUtil.addPaths(base,"../"));
|
buf.append(addPaths(base,"../"));
|
||||||
buf.append("\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "up.png\"> ")
|
buf.append("\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "up.png\"> ")
|
||||||
.append(_("Up to higher level directory"))
|
.append(_("Up to higher level directory"))
|
||||||
.append("</A></td></tr>\n");
|
.append("</A></td></tr>\n");
|
||||||
@ -2239,12 +2233,12 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
boolean showSaveButton = false;
|
boolean showSaveButton = false;
|
||||||
for (int i=0 ; i< ls.length ; i++)
|
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)
|
// 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
|
// http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs
|
||||||
// See resource.diff attachment
|
// See resource.diff attachment
|
||||||
//Resource item = addPath(encoded);
|
//Resource item = addPath(encoded);
|
||||||
Resource item = r.addPath(ls[i]);
|
File item = new File(r, ls[i]);
|
||||||
|
|
||||||
String rowClass = (i % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
|
String rowClass = (i % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
|
||||||
buf.append("<TR class=\"").append(rowClass).append("\">");
|
buf.append("<TR class=\"").append(rowClass).append("\">");
|
||||||
@ -2264,7 +2258,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
} else {
|
} else {
|
||||||
Storage storage = snark.getStorage();
|
Storage storage = snark.getStorage();
|
||||||
try {
|
try {
|
||||||
File f = item.getFile();
|
File f = item;
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
long remaining = storage.remaining(f.getCanonicalPath());
|
long remaining = storage.remaining(f.getCanonicalPath());
|
||||||
if (remaining < 0) {
|
if (remaining < 0) {
|
||||||
@ -2294,9 +2288,9 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String path=URIUtil.addPaths(base,encoded);
|
String path=addPaths(base,encoded);
|
||||||
if (item.isDirectory() && !path.endsWith("/"))
|
if (item.isDirectory() && !path.endsWith("/"))
|
||||||
path=URIUtil.addPaths(path,"/");
|
path=addPaths(path,"/");
|
||||||
String icon = toIcon(item);
|
String icon = toIcon(item);
|
||||||
|
|
||||||
buf.append("<TD class=\"snarkFileIcon ")
|
buf.append("<TD class=\"snarkFileIcon ")
|
||||||
@ -2331,7 +2325,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
buf.append("</TD>");
|
buf.append("</TD>");
|
||||||
if (showPriority) {
|
if (showPriority) {
|
||||||
buf.append("<td class=\"priority\">");
|
buf.append("<td class=\"priority\">");
|
||||||
File f = item.getFile();
|
File f = item;
|
||||||
if ((!complete) && (!item.isDirectory()) && f != null) {
|
if ((!complete) && (!item.isDirectory()) && f != null) {
|
||||||
int pri = snark.getStorage().getPriority(f.getCanonicalPath());
|
int pri = snark.getStorage().getPriority(f.getCanonicalPath());
|
||||||
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
|
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
|
||||||
@ -2368,7 +2362,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @since 0.7.14 */
|
/** @since 0.7.14 */
|
||||||
private String toIcon(Resource item) {
|
private String toIcon(File item) {
|
||||||
if (item.isDirectory())
|
if (item.isDirectory())
|
||||||
return "folder";
|
return "folder";
|
||||||
return toIcon(item.toString());
|
return toIcon(item.toString());
|
||||||
@ -2381,40 +2375,32 @@ public class I2PSnarkServlet extends DefaultServlet {
|
|||||||
*/
|
*/
|
||||||
private String toIcon(String path) {
|
private String toIcon(String path) {
|
||||||
String icon;
|
String icon;
|
||||||
// Should really just add to the mime.properties file in org.mortbay.jetty.jar
|
// Note that for this to work well, our custom mime.properties file must be loaded.
|
||||||
// instead of this mishmash. We can't get to HttpContext.setMimeMapping()
|
|
||||||
// from here? We could do it from a web.xml perhaps.
|
|
||||||
// Or could we put our own org/mortbay/http/mime.properties file in the war?
|
|
||||||
String plc = path.toLowerCase(Locale.US);
|
String plc = path.toLowerCase(Locale.US);
|
||||||
String mime = getServletContext().getMimeType(path);
|
String mime = getMimeType(path);
|
||||||
if (mime == null)
|
if (mime == null)
|
||||||
mime = "";
|
mime = "";
|
||||||
if (mime.equals("text/html"))
|
if (mime.equals("text/html"))
|
||||||
icon = "html";
|
icon = "html";
|
||||||
else if (mime.equals("text/plain") || plc.endsWith(".nfo") ||
|
else if (mime.equals("text/plain") ||
|
||||||
mime.equals("application/rtf"))
|
mime.equals("application/rtf"))
|
||||||
icon = "page";
|
icon = "page";
|
||||||
else if (mime.equals("application/java-archive") || plc.endsWith(".war") ||
|
else if (mime.equals("application/java-archive") ||
|
||||||
plc.endsWith(".deb"))
|
plc.endsWith(".deb"))
|
||||||
icon = "package";
|
icon = "package";
|
||||||
else if (plc.endsWith(".xpi2p"))
|
else if (plc.endsWith(".xpi2p"))
|
||||||
icon = "plugin";
|
icon = "plugin";
|
||||||
else if (mime.equals("application/pdf"))
|
else if (mime.equals("application/pdf"))
|
||||||
icon = "page_white_acrobat";
|
icon = "page_white_acrobat";
|
||||||
else if (mime.startsWith("image/") || plc.endsWith(".ico"))
|
else if (mime.startsWith("image/"))
|
||||||
icon = "photo";
|
icon = "photo";
|
||||||
else if (mime.startsWith("audio/") || mime.equals("application/ogg") ||
|
else if (mime.startsWith("audio/") || mime.equals("application/ogg"))
|
||||||
plc.endsWith(".flac") || plc.endsWith(".m4a") || plc.endsWith(".wma") ||
|
|
||||||
plc.endsWith(".ape") || plc.endsWith(".oga"))
|
|
||||||
icon = "music";
|
icon = "music";
|
||||||
else if (mime.startsWith("video/") || plc.endsWith(".mkv") || plc.endsWith(".m4v") ||
|
else if (mime.startsWith("video/"))
|
||||||
plc.endsWith(".mp4") || plc.endsWith(".wmv") || plc.endsWith(".flv") ||
|
|
||||||
plc.endsWith(".ogm") || plc.endsWith(".ogv"))
|
|
||||||
icon = "film";
|
icon = "film";
|
||||||
else if (mime.equals("application/zip") || mime.equals("application/x-gtar") ||
|
else if (mime.equals("application/zip") || mime.equals("application/x-gtar") ||
|
||||||
mime.equals("application/compress") || mime.equals("application/gzip") ||
|
mime.equals("application/compress") || mime.equals("application/gzip") ||
|
||||||
mime.equals("application/x-tar") ||
|
mime.equals("application/x-tar"))
|
||||||
plc.endsWith(".rar") || plc.endsWith(".bz2") || plc.endsWith(".7z"))
|
|
||||||
icon = "compress";
|
icon = "compress";
|
||||||
else if (plc.endsWith(".exe"))
|
else if (plc.endsWith(".exe"))
|
||||||
icon = "application";
|
icon = "application";
|
||||||
|
131
apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java
Normal file
131
apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java
Normal file
@ -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<String, String> _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<String, String> 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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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); }
|
||||||
|
}
|
17
apps/i2psnark/mime.properties
Normal file
17
apps/i2psnark/mime.properties
Normal file
@ -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
|
Reference in New Issue
Block a user