content serving servlet, thx to zzz
This commit is contained in:
@ -20,6 +20,9 @@ war {
|
||||
from ('src/main/js', {
|
||||
into "js"
|
||||
})
|
||||
from ('src/main/resources', {
|
||||
into "WEB-INF/classes/com/muwire/webui"
|
||||
})
|
||||
webInf {
|
||||
from "$buildDir/compiledJsps"
|
||||
into "classes"
|
||||
@ -100,6 +103,7 @@ task poupdate {
|
||||
precompileJsp.dependsOn compileJava
|
||||
generateWebXML.dependsOn precompileJsp
|
||||
bundle.dependsOn precompileJsp
|
||||
poupdate.dependsOn precompileJsp
|
||||
war.dependsOn generateWebXML, bundle
|
||||
|
||||
artifacts {
|
||||
|
534
webui/src/main/java/com/muwire/webui/BasicServlet.java
Normal file
534
webui/src/main/java/com/muwire/webui/BasicServlet.java
Normal file
@ -0,0 +1,534 @@
|
||||
// ========================================================================
|
||||
// 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 com.muwire.webui;
|
||||
|
||||
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 java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
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.data.DataHelper;
|
||||
import net.i2p.servlet.util.WriterOutputStream;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* 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.
|
||||
* Supports single request ranges.
|
||||
*
|
||||
* Does not support directories or "welcome files".
|
||||
* Does not support gzip.
|
||||
* Does not support multiple request ranges.
|
||||
* Does not cache.
|
||||
*
|
||||
* POST returns 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
|
||||
* on the servlet itself:
|
||||
* <PRE>
|
||||
*
|
||||
* resourceBase Set to replace the context resource base
|
||||
|
||||
* </PRE>
|
||||
*
|
||||
*
|
||||
* @author Greg Wilkins (gregw)
|
||||
* @author Nigel Canonizado
|
||||
*
|
||||
* @since Jetty 7
|
||||
*/
|
||||
class BasicServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
protected transient final I2PAppContext _context;
|
||||
protected transient final Log _log;
|
||||
protected File _resourceBase;
|
||||
|
||||
private transient final MimeTypes _mimeTypes;
|
||||
|
||||
/** same as PeerState.PARTSIZE */
|
||||
private static final int BUFSIZE = 16*1024;
|
||||
private transient ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
||||
|
||||
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 SecureFile(rb);
|
||||
setResourceBase(f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Files are served from here
|
||||
*/
|
||||
protected synchronized void setResourceBase(File base) throws UnavailableException {
|
||||
if (!base.isDirectory()) {
|
||||
_log.error("Configured directory " + base + " does not exist");
|
||||
//throw new UnavailableException("Resource base does not exist: " + base);
|
||||
}
|
||||
_resourceBase = base;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Resource base is " + _resourceBase);
|
||||
}
|
||||
|
||||
/** 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)
|
||||
{
|
||||
File r = null;
|
||||
if (!pathInContext.contains("..") &&
|
||||
!pathInContext.endsWith("/")) {
|
||||
File f;
|
||||
synchronized (this) {
|
||||
if (_resourceBase==null)
|
||||
return null;
|
||||
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)
|
||||
{
|
||||
HttpContent r = null;
|
||||
File f = getResource(pathInContext);
|
||||
// exists && !directory
|
||||
if (f != null && 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)
|
||||
{
|
||||
try {
|
||||
response.reset();
|
||||
} catch (IllegalStateException ise) {
|
||||
// committed
|
||||
return true;
|
||||
}
|
||||
response.setStatus(304);
|
||||
response.getOutputStream().close();
|
||||
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());
|
||||
}
|
||||
|
||||
long content_length = content.getContentLength();
|
||||
|
||||
// see if there are any range headers
|
||||
Enumeration<?> reqRanges = request.getHeaders("Range");
|
||||
|
||||
if (reqRanges == null || !reqRanges.hasMoreElements()) {
|
||||
// if there were no ranges, send entire entity
|
||||
// Write content normally
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// Parse the satisfiable ranges
|
||||
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges(reqRanges, content_length);
|
||||
|
||||
// if there are no satisfiable ranges, send 416 response
|
||||
// Completely punt on multiple ranges (unlike Default)
|
||||
if (ranges == null || ranges.size() != 1) {
|
||||
writeHeaders(response, content, content_length);
|
||||
response.setStatus(416);
|
||||
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
in.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is only a single valid range (must be satisfiable
|
||||
// since were here now), send that range with a 216 response
|
||||
InclusiveByteRange singleSatisfiableRange = ranges.get(0);
|
||||
long singleLength = singleSatisfiableRange.getSize(content_length);
|
||||
writeHeaders(response, content, singleLength);
|
||||
response.setStatus(206);
|
||||
response.setHeader("Content-Range", singleSatisfiableRange.toHeaderRangeString(content_length));
|
||||
copy(in, singleSatisfiableRange.getFirst(content_length), out, singleLength);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
|
||||
throws IOException
|
||||
{
|
||||
String rtype = response.getContentType();
|
||||
String ctype = content.getContentType();
|
||||
if (rtype != null) {
|
||||
if (rtype.equals("application/javascript"))
|
||||
response.setCharacterEncoding("ISO-8859-1");
|
||||
} else if (ctype != null) {
|
||||
response.setContentType(ctype);
|
||||
if (ctype.equals("application/javascript"))
|
||||
response.setCharacterEncoding("ISO-8859-1");
|
||||
}
|
||||
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||
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));
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
} else {
|
||||
response.setHeader("Accept-Ranges", "none");
|
||||
}
|
||||
|
||||
// add name header for muwire since the URL is just the hash
|
||||
String name = content.getContentName();
|
||||
String name2 = FilenameUtil.sanitizeFilename(name);
|
||||
String name3 = FilenameUtil.encodeFilenameRFC5987(name);
|
||||
response.addHeader("Content-Disposition", "inline; filename=\"" + name2 + "\"; " +
|
||||
"filename*=" + name3);
|
||||
|
||||
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();
|
||||
String getContentName();
|
||||
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 String getContentName()
|
||||
{
|
||||
return _file.getName();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
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 + '"'; }
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
String rv = (new File(base, path)).toString();
|
||||
if (SystemVersion.isWindows())
|
||||
rv = rv.replace("\\", "/");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write from in to out
|
||||
*/
|
||||
private void copy(InputStream in, OutputStream out) throws IOException {
|
||||
copy(in, 0, out, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write from in to out
|
||||
*/
|
||||
private void copy(InputStream in, long skip, OutputStream out, final long len) throws IOException {
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte[] buf = ba.getData();
|
||||
try {
|
||||
if (skip > 0)
|
||||
DataHelper.skip(in, skip);
|
||||
int read = 0;
|
||||
long tot = 0;
|
||||
boolean done = false;
|
||||
while ( (read = in.read(buf)) != -1 && !done) {
|
||||
if (len >= 0) {
|
||||
tot += read;
|
||||
if (tot >= len) {
|
||||
read -= (int) (tot - len);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.muwire.webui;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
public class DownloadedContentServlet extends BasicServlet {
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
loadMimeMap("com/muwire/webui/mime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the file for the hash.
|
||||
*
|
||||
* @param pathInContext should always start with /
|
||||
* @return file or null
|
||||
*/
|
||||
@Override
|
||||
public File getResource(String pathInContext)
|
||||
{
|
||||
File r = null;
|
||||
// TODO
|
||||
return r;
|
||||
}
|
||||
}
|
87
webui/src/main/java/com/muwire/webui/FilenameUtil.java
Normal file
87
webui/src/main/java/com/muwire/webui/FilenameUtil.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.muwire.webui;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* File name encoding methods
|
||||
*
|
||||
* From SusiMail. GPLv2 or any later version.
|
||||
*/
|
||||
public class FilenameUtil {
|
||||
|
||||
/**
|
||||
* Convert the UTF-8 to ASCII suitable for inclusion in a header
|
||||
* and for use as a cross-platform filename.
|
||||
* Replace chars likely to be illegal in filenames,
|
||||
* and non-ASCII chars, with _
|
||||
*
|
||||
* Ref: RFC 6266, RFC 5987, i2psnark Storage.ILLEGAL
|
||||
*
|
||||
* @since 0.9.18
|
||||
*/
|
||||
public static String sanitizeFilename(String name) {
|
||||
name = name.trim();
|
||||
StringBuilder buf = new StringBuilder(name.length());
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
// illegal filename chars
|
||||
if (c <= 32 || c >= 0x7f ||
|
||||
c == '<' || c == '>' || c == ':' || c == '"' ||
|
||||
c == '/' || c == '\\' || c == '|' || c == '?' ||
|
||||
c == '*')
|
||||
buf.append('_');
|
||||
else
|
||||
buf.append(c);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the UTF-8 suitable for inclusion in a header
|
||||
* as a RFC 5987/6266 filename* value, and for use as a cross-platform filename.
|
||||
* Replace chars likely to be illegal in filenames with _
|
||||
*
|
||||
* Ref: RFC 6266, RFC 5987, i2psnark Storage.ILLEGAL
|
||||
*
|
||||
* This does NOT do multiline, e.g. filename*0* (RFC 2231)
|
||||
*
|
||||
* ref: https://blog.nodemailer.com/2017/01/27/the-mess-that-is-attachment-filenames/
|
||||
* ref: RFC 2231
|
||||
*
|
||||
* @since 0.9.33
|
||||
*/
|
||||
public static String encodeFilenameRFC5987(String name) {
|
||||
name = name.trim();
|
||||
StringBuilder buf = new StringBuilder(name.length());
|
||||
buf.append("utf-8''");
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
// illegal filename chars
|
||||
if (c < 32 || (c >= 0x7f && c <= 0x9f) ||
|
||||
c == '<' || c == '>' || c == ':' || c == '"' ||
|
||||
c == '/' || c == '\\' || c == '|' || c == '?' ||
|
||||
c == '*' ||
|
||||
// unicode newlines
|
||||
c == 0x2028 || c == 0x2029) {
|
||||
buf.append('_');
|
||||
} else if (c == ' ' || c == '\'' || c == '%' || // not in 5987 attr-char
|
||||
c == '(' || c == ')' || c == '@' || // 2616 separators
|
||||
c == ',' || c == ';' || c == '[' || c == ']' ||
|
||||
c == '=' || c == '{' || c == '}') {
|
||||
// single byte encoding
|
||||
buf.append(HexTable.table[c].replace('=', '%'));
|
||||
} else if (c < 0x7f) {
|
||||
// single byte char, as-is
|
||||
buf.append(c);
|
||||
} else {
|
||||
// multi-byte encoding
|
||||
byte[] utf = DataHelper.getUTF8(String.valueOf(c));
|
||||
for (int j = 0; j < utf.length; j++) {
|
||||
int b = utf[j] & 0xff;
|
||||
buf.append(HexTable.table[b].replace('=', '%'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
73
webui/src/main/java/com/muwire/webui/HexTable.java
Normal file
73
webui/src/main/java/com/muwire/webui/HexTable.java
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Created on Nov 12, 2004
|
||||
*
|
||||
* This file is part of susimail project, see http://susi.i2p/
|
||||
*
|
||||
* Copyright (C) 2004-2005 <susi23@mail.i2p>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
package com.muwire.webui;
|
||||
|
||||
/**
|
||||
* From SusiMail.
|
||||
*
|
||||
* @author susi
|
||||
*/
|
||||
public class HexTable {
|
||||
|
||||
/**
|
||||
* Three character strings, upper case, e.g. "=0A"
|
||||
*/
|
||||
public static final String[] table = new String[256];
|
||||
|
||||
static {
|
||||
for( int i = 0; i < 256; i++ ) {
|
||||
String str = intToHex( i );
|
||||
if( str.length() == 1 )
|
||||
str = "0" + str;
|
||||
table[i] = "=" + str;
|
||||
}
|
||||
}
|
||||
|
||||
private static String intToHex( int b )
|
||||
{
|
||||
if( b == 0 )
|
||||
return "0";
|
||||
else {
|
||||
String str = "";
|
||||
while( b > 0 ) {
|
||||
byte c = (byte)(b % 16);
|
||||
if( c < 10 )
|
||||
c += '0';
|
||||
else
|
||||
c += 'A' - 10;
|
||||
str = "" + (char)c + str;
|
||||
b = (byte)(b / 16);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
for( int i = 0; i < 256; i++ ) {
|
||||
System.out.println(i + ": " + table[i]);
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
218
webui/src/main/java/com/muwire/webui/InclusiveByteRange.java
Normal file
218
webui/src/main/java/com/muwire/webui/InclusiveByteRange.java
Normal file
@ -0,0 +1,218 @@
|
||||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package com.muwire.webui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Byte range inclusive of end points.
|
||||
* <PRE>
|
||||
*
|
||||
* parses the following types of byte ranges:
|
||||
*
|
||||
* bytes=100-499
|
||||
* bytes=-300
|
||||
* bytes=100-
|
||||
* bytes=1-2,2-3,6-,-2
|
||||
*
|
||||
* given an entity length, converts range to string
|
||||
*
|
||||
* bytes 100-499/500
|
||||
*
|
||||
* </PRE>
|
||||
*
|
||||
* Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
|
||||
* @version $version$
|
||||
*
|
||||
*/
|
||||
public class InclusiveByteRange
|
||||
{
|
||||
long first = 0;
|
||||
long last = 0;
|
||||
|
||||
public InclusiveByteRange(long first, long last)
|
||||
{
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public long getFirst()
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
public long getLast()
|
||||
{
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param headers Enumeration of Range header fields.
|
||||
* @param size Size of the resource.
|
||||
* @return List of satisfiable ranges
|
||||
*/
|
||||
public static List<InclusiveByteRange> satisfiableRanges(Enumeration<?> headers, long size)
|
||||
{
|
||||
List<InclusiveByteRange> satRanges = null;
|
||||
|
||||
// walk through all Range headers
|
||||
headers:
|
||||
while (headers.hasMoreElements())
|
||||
{
|
||||
String header = (String) headers.nextElement();
|
||||
StringTokenizer tok = new StringTokenizer(header,"=,",false);
|
||||
String t=null;
|
||||
try
|
||||
{
|
||||
// read all byte ranges for this header
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
try
|
||||
{
|
||||
t = tok.nextToken().trim();
|
||||
|
||||
long first = -1;
|
||||
long last = -1;
|
||||
int d = t.indexOf('-');
|
||||
if (d < 0 || t.indexOf('-',d + 1) >= 0)
|
||||
{
|
||||
if ("bytes".equals(t))
|
||||
continue;
|
||||
continue headers;
|
||||
}
|
||||
else if (d == 0)
|
||||
{
|
||||
if (d + 1 < t.length())
|
||||
last = Long.parseLong(t.substring(d + 1).trim());
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (d + 1 < t.length())
|
||||
{
|
||||
first = Long.parseLong(t.substring(0,d).trim());
|
||||
last = Long.parseLong(t.substring(d + 1).trim());
|
||||
}
|
||||
else
|
||||
first = Long.parseLong(t.substring(0,d).trim());
|
||||
|
||||
if (first == -1 && last == -1)
|
||||
continue headers;
|
||||
|
||||
if (first != -1 && last != -1 && (first > last))
|
||||
continue headers;
|
||||
|
||||
if (first < size)
|
||||
{
|
||||
if (satRanges == null)
|
||||
satRanges = new ArrayList<InclusiveByteRange>(4);
|
||||
InclusiveByteRange range = new InclusiveByteRange(first,last);
|
||||
satRanges.add(range);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return satRanges;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getFirst(long size)
|
||||
{
|
||||
if (first<0)
|
||||
{
|
||||
long tf=size-last;
|
||||
if (tf<0)
|
||||
tf=0;
|
||||
return tf;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getLast(long size)
|
||||
{
|
||||
if (first<0)
|
||||
return size-1;
|
||||
|
||||
if (last<0 ||last>=size)
|
||||
return size-1;
|
||||
return last;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getSize(long size)
|
||||
{
|
||||
return getLast(size)-getFirst(size)+1;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String toHeaderRangeString(long size)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append("bytes ");
|
||||
sb.append(getFirst(size));
|
||||
sb.append('-');
|
||||
sb.append(getLast(size));
|
||||
sb.append("/");
|
||||
sb.append(size);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static String to416HeaderRangeString(long size)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append("bytes */");
|
||||
sb.append(size);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(60);
|
||||
sb.append(Long.toString(first));
|
||||
sb.append(":");
|
||||
sb.append(Long.toString(last));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
129
webui/src/main/java/com/muwire/webui/MimeTypes.java
Normal file
129
webui/src/main/java/com/muwire/webui/MimeTypes.java
Normal file
@ -0,0 +1,129 @@
|
||||
// ========================================================================
|
||||
// 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 com.muwire.webui;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* 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<String, String>();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @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,29 @@
|
||||
package com.muwire.webui;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
public class SharedContentServlet extends BasicServlet {
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
loadMimeMap("com/muwire/webui/mime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the file for the hash.
|
||||
*
|
||||
* @param pathInContext should always start with /
|
||||
* @return file or null
|
||||
*/
|
||||
@Override
|
||||
public File getResource(String pathInContext)
|
||||
{
|
||||
File r = null;
|
||||
// TODO
|
||||
return r;
|
||||
}
|
||||
}
|
59
webui/src/main/resources/mime.properties
Normal file
59
webui/src/main/resources/mime.properties
Normal file
@ -0,0 +1,59 @@
|
||||
3gp = video/3gpp
|
||||
3gpp = video/3gpp
|
||||
7z = application/x-7z-compressed
|
||||
ape = audio/x-monkeys-audio
|
||||
bz2 = application/x-bzip2
|
||||
cue = application/x-cue
|
||||
dmg = application/apple-diskimage
|
||||
epub = application/epub+zip
|
||||
flac = audio/flac
|
||||
flv = video/x-flv
|
||||
iso = application/x-iso9660-image
|
||||
m3u = audio/mpegurl
|
||||
m3u8 = audio/mpegurl
|
||||
m4a = audio/mp4a-latm
|
||||
m4b = audio/mp4a-latm
|
||||
m4v = video/x-m4v
|
||||
mka = audio/x-matroska
|
||||
mkv = video/x-matroska
|
||||
mobi = application/x-mobipocket-ebook
|
||||
mp4 = video/mp4
|
||||
mpc = audio/x-musepack
|
||||
nfo = text/plain
|
||||
odb = application/vnd.oasis.opendocument.database
|
||||
odc = application/vnd.oasis.opendocument.chart
|
||||
odf = application/vnd.oasis.opendocument.formula
|
||||
odg = application/vnd.oasis.opendocument.graphics
|
||||
odi = application/vnd.oasis.opendocument.image
|
||||
odm = application/vnd.oasis.opendocument.text-master
|
||||
odp = application/vnd.oasis.opendocument.presentation
|
||||
ods = application/vnd.oasis.opendocument.spreadsheet
|
||||
odt = application/vnd.oasis.opendocument.text
|
||||
ogm = video/ogg
|
||||
ogv = video/ogg
|
||||
oga = audio/ogg
|
||||
opus = audio/ogg
|
||||
otc = application/vnd.oasis.opendocument.chart-template
|
||||
otf = application/vnd.oasis.opendocument.formula-template
|
||||
otg = application/vnd.oasis.opendocument.graphics-template
|
||||
oth = application/vnd.oasis.opendocument.text-web
|
||||
oti = application/vnd.oasis.opendocument.image-template
|
||||
otp = application/vnd.oasis.opendocument.presentation-template
|
||||
ots = application/vnd.oasis.opendocument.spreadsheet-template
|
||||
ott = application/vnd.oasis.opendocument.text-template
|
||||
pls = audio/x-scpls
|
||||
rar = application/x-rar-compressed
|
||||
sfv = text/x-sfv
|
||||
su2 = application/zip
|
||||
su3 = application/zip
|
||||
sud = application/zip
|
||||
tbz = application/x-bzip2
|
||||
torrent = application/x-bittorrent
|
||||
txt = text/plain
|
||||
war = application/java-archive
|
||||
webm = video/webm
|
||||
wma = audio/x-ms-wma
|
||||
wmv = video/x-ms-wmv
|
||||
wpl = application/vnd.ms-wpl
|
||||
xspf = application/xspf+xml
|
||||
xz = application/x-xz
|
Reference in New Issue
Block a user