Update NanoHTTPd to 12b4973a52

This commit is contained in:
StevenLawson 2013-09-17 21:31:46 -04:00
parent 7b59350833
commit e10ab45bda
11 changed files with 539 additions and 325 deletions

View file

@ -1,5 +1,5 @@
#Tue, 17 Sep 2013 12:03:45 -0400 #Tue, 17 Sep 2013 21:28:11 -0400
program.VERSION=3.2 program.VERSION=3.2
program.BUILDNUM=595 program.BUILDNUM=596
program.BUILDDATE=09/17/2013 12\:03 PM program.BUILDDATE=09/17/2013 09\:28 PM

View file

@ -1,3 +1,3 @@
#Build Number for ANT. Do not edit! #Build Number for ANT. Do not edit!
#Tue Sep 17 12:03:45 EDT 2013 #Tue Sep 17 21:28:11 EDT 2013
build.number=596 build.number=597

View file

@ -2,7 +2,6 @@ package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.Socket;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -16,9 +15,9 @@ public class Module_dump extends TFM_HTTPD_Module
private File echoFile = null; private File echoFile = null;
private final String body; private final String body;
public Module_dump(String uri, NanoHTTPD.Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_dump(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
//Body needs to be computed before getResponse, so we know if a text response or a file echo is needed. //Body needs to be computed before getResponse, so we know if a text response or a file echo is needed.
this.body = body(); this.body = body();
@ -54,6 +53,8 @@ public class Module_dump extends TFM_HTTPD_Module
String[] args = StringUtils.split(uri, "/"); String[] args = StringUtils.split(uri, "/");
Map<String, String> files = getFiles();
responseBody responseBody
.append(paragraph("URI: " + uri)) .append(paragraph("URI: " + uri))
.append(paragraph("args (Length: " + args.length + "): " + StringUtils.join(args, ","))) .append(paragraph("args (Length: " + args.length + "): " + StringUtils.join(args, ",")))

View file

@ -5,7 +5,6 @@ import java.io.FileInputStream;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -13,9 +12,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry;
import static me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.*; import static me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.*;
import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry;
/* /*
* This class was adapted from https://github.com/NanoHttpd/nanohttpd/blob/master/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java * This class was adapted from https://github.com/NanoHttpd/nanohttpd/blob/master/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
@ -55,9 +54,9 @@ public class Module_file extends TFM_HTTPD_Module
MIME_TYPES.put("class", "application/octet-stream"); MIME_TYPES.put("class", "application/octet-stream");
} }
public Module_file(String uri, Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_file(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
} }
private File getRootDir() private File getRootDir()
@ -175,7 +174,7 @@ public class Module_file extends TFM_HTTPD_Module
} }
if (mime == null) if (mime == null)
{ {
mime = NanoHTTPD.MIME_DEFAULT_BINARY; mime = TFM_HTTPD_Manager.MIME_DEFAULT_BINARY;
} }
// Calculate etag // Calculate etag

View file

@ -1,6 +1,5 @@
package me.StevenLawson.TotalFreedomMod.HTTPD; package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -21,9 +20,9 @@ import static org.apache.commons.lang3.StringEscapeUtils.*;
public class Module_help extends TFM_HTTPD_Module public class Module_help extends TFM_HTTPD_Module
{ {
public Module_help(String uri, NanoHTTPD.Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_help(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
} }
@Override @Override

View file

@ -1,7 +1,5 @@
package me.StevenLawson.TotalFreedomMod.HTTPD; package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.net.Socket;
import java.util.Map;
import me.StevenLawson.TotalFreedomMod.TFM_SuperadminList; import me.StevenLawson.TotalFreedomMod.TFM_SuperadminList;
import me.StevenLawson.TotalFreedomMod.TFM_Util; import me.StevenLawson.TotalFreedomMod.TFM_Util;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -9,9 +7,9 @@ import org.bukkit.entity.Player;
public class Module_list extends TFM_HTTPD_Module public class Module_list extends TFM_HTTPD_Module
{ {
public Module_list(String uri, NanoHTTPD.Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_list(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
} }
@Override @Override

View file

@ -1,15 +1,13 @@
package me.StevenLawson.TotalFreedomMod.HTTPD; package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.io.File; import java.io.File;
import java.net.Socket;
import java.util.Map;
import me.StevenLawson.TotalFreedomMod.TotalFreedomMod; import me.StevenLawson.TotalFreedomMod.TotalFreedomMod;
public class Module_permbans extends TFM_HTTPD_Module public class Module_permbans extends TFM_HTTPD_Module
{ {
public Module_permbans(String uri, NanoHTTPD.Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_permbans(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
} }
@Override @Override

View file

@ -2,7 +2,6 @@ package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -16,8 +15,8 @@ import me.StevenLawson.TotalFreedomMod.TFM_Log;
import me.StevenLawson.TotalFreedomMod.TFM_Superadmin; import me.StevenLawson.TotalFreedomMod.TFM_Superadmin;
import me.StevenLawson.TotalFreedomMod.TFM_SuperadminList; import me.StevenLawson.TotalFreedomMod.TFM_SuperadminList;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
public class Module_schematic extends TFM_HTTPD_Module public class Module_schematic extends TFM_HTTPD_Module
{ {
@ -36,9 +35,9 @@ public class Module_schematic extends TFM_HTTPD_Module
+ "<button type=\"submit\">Submit</button>\n" + "<button type=\"submit\">Submit</button>\n"
+ "</form>"; + "</form>";
public Module_schematic(String uri, Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public Module_schematic(NanoHTTPD.HTTPSession session)
{ {
super(uri, method, headers, params, files, socket); super(session);
} }
@Override @Override
@ -153,6 +152,8 @@ public class Module_schematic extends TFM_HTTPD_Module
private boolean uploadSchematic() throws SchematicTransferException private boolean uploadSchematic() throws SchematicTransferException
{ {
Map<String, String> files = getFiles();
final String tempFileName = files.get(REQUEST_FORM_FILE_ELEMENT_NAME); final String tempFileName = files.get(REQUEST_FORM_FILE_ELEMENT_NAME);
if (tempFileName == null) if (tempFileName == null)
{ {

View file

@ -1,11 +1,7 @@
package me.StevenLawson.TotalFreedomMod.HTTPD; package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.io.*; import java.io.*;
import java.net.InetSocketAddress; import java.net.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URLDecoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -67,17 +63,21 @@ public abstract class NanoHTTPD
*/ */
public static final String MIME_HTML = "text/html"; public static final String MIME_HTML = "text/html";
/** /**
* Common mime type for dynamic content: binary * Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing.
*/ */
public static final String MIME_DEFAULT_BINARY = "application/octet-stream"; private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
private final String hostname; private final String hostname;
private final int myPort; private final int myPort;
private ServerSocket myServerSocket; private ServerSocket myServerSocket;
private Thread myThread; private Thread myThread;
/** /**
* Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing. * Pluggable strategy for asynchronously executing requests.
*/ */
private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; private AsyncRunner asyncRunner;
/**
* Pluggable strategy for creating and cleaning up temporary files.
*/
private TempFileManagerFactory tempFileManagerFactory;
/** /**
* Constructs an HTTP server on given port. * Constructs an HTTP server on given port.
@ -98,8 +98,51 @@ public abstract class NanoHTTPD
setAsyncRunner(new DefaultAsyncRunner()); setAsyncRunner(new DefaultAsyncRunner());
} }
private static final void safeClose(ServerSocket serverSocket)
{
if (serverSocket != null)
{
try
{
serverSocket.close();
}
catch (IOException e)
{
}
}
}
private static final void safeClose(Socket socket)
{
if (socket != null)
{
try
{
socket.close();
}
catch (IOException e)
{
}
}
}
private static final void safeClose(Closeable closeable)
{
if (closeable != null)
{
try
{
closeable.close();
}
catch (IOException e)
{
}
}
}
/** /**
* Start the server. * Start the server.
*
* @throws IOException if the socket is in use. * @throws IOException if the socket is in use.
*/ */
public void start() throws IOException public void start() throws IOException
@ -187,20 +230,39 @@ public abstract class NanoHTTPD
} }
} }
public final int getListeningPort()
{
return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
}
public final boolean wasStarted()
{
return myServerSocket != null && myThread != null;
}
public final boolean isAlive()
{
return wasStarted() && !myServerSocket.isClosed() && myThread.isAlive();
}
/** /**
* Override this to customize the server. * Override this to customize the server.
* <p/> * <p/>
* <p/> * <p/>
* (By default, this delegates to serveFile() and allows directory listing.) * (By default, this delegates to serveFile() and allows directory listing.)
* *
* @param uri Percent-decoded URI without parameters, for example "/index.cgi" * @param uri Percent-decoded URI without parameters, for example "/index.cgi"
* @param method "GET", "POST" etc. * @param method "GET", "POST" etc.
* @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data.
* @param headers Header entries, percent decoded * @param headers Header entries, percent decoded
* @return HTTP response, see class Response for details * @return HTTP response, see class Response for details
*/ */
public abstract Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, @Deprecated
Map<String, String> files, Socket socket); public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,
Map<String, String> files)
{
return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
}
/** /**
* Override this to customize the server. * Override this to customize the server.
@ -211,33 +273,32 @@ public abstract class NanoHTTPD
* @param session The HTTP session * @param session The HTTP session
* @return HTTP response, see class Response for details * @return HTTP response, see class Response for details
*/ */
protected Response serve(HTTPSession session) public Response serve(HTTPSession session)
{ {
Map<String, String> files = new HashMap<String, String>(); Map<String, String> files = new HashMap<String, String>();
try
{
session.parseBody(files);
}
catch (IOException ioe)
{
return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}
catch (ResponseException re)
{
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
String uri = session.getUri();
Method method = session.getMethod(); Method method = session.getMethod();
Map<String, String> parms = session.getParms(); if (Method.PUT.equals(method) || Method.POST.equals(method))
Map<String, String> headers = session.getHeaders(); {
Socket socket = session.getSocket(); try
return serve(uri, method, headers, parms, files, socket); {
session.parseBody(files);
}
catch (IOException ioe)
{
return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}
catch (ResponseException re)
{
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
}
return serve(session.getUri(), method, session.getHeaders(), session.getParms(), files);
} }
/** /**
* Decode percent encoded <code>String</code> values. * Decode percent encoded <code>String</code> values.
*
* @param str the percent encoded <code>String</code> * @param str the percent encoded <code>String</code>
* @return expanded form of the input, for example "foo%20bar" becomes "foo bar" * @return expanded form of the input, for example "foo%20bar" becomes "foo bar"
*/ */
@ -300,6 +361,36 @@ public abstract class NanoHTTPD
return parms; return parms;
} }
// ------------------------------------------------------------------------------- //
//
// Threading Strategy.
//
// ------------------------------------------------------------------------------- //
/**
* Pluggable strategy for asynchronously executing requests.
*
* @param asyncRunner new strategy for handling threads.
*/
public void setAsyncRunner(AsyncRunner asyncRunner)
{
this.asyncRunner = asyncRunner;
}
// ------------------------------------------------------------------------------- //
//
// Temp file handling strategy.
//
// ------------------------------------------------------------------------------- //
/**
* Pluggable strategy for creating and cleaning up temporary files.
*
* @param tempFileManagerFactory new strategy for handling temp files.
*/
public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory)
{
this.tempFileManagerFactory = tempFileManagerFactory;
}
/** /**
* HTTP Request methods, with the ability to decode a <code>String</code> back to its enum value. * HTTP Request methods, with the ability to decode a <code>String</code> back to its enum value.
*/ */
@ -319,24 +410,6 @@ public abstract class NanoHTTPD
return null; return null;
} }
} }
// ------------------------------------------------------------------------------- //
//
// Threading Strategy.
//
// ------------------------------------------------------------------------------- //
/**
* Pluggable strategy for asynchronously executing requests.
*/
private AsyncRunner asyncRunner;
/**
* Pluggable strategy for asynchronously executing requests.
* @param asyncRunner new strategy for handling threads.
*/
public void setAsyncRunner(AsyncRunner asyncRunner)
{
this.asyncRunner = asyncRunner;
}
/** /**
* Pluggable strategy for asynchronously executing requests. * Pluggable strategy for asynchronously executing requests.
@ -346,9 +419,46 @@ public abstract class NanoHTTPD
void exec(Runnable code); void exec(Runnable code);
} }
/**
* Factory to create temp file managers.
*/
public interface TempFileManagerFactory
{
TempFileManager create();
}
// ------------------------------------------------------------------------------- //
/**
* Temp file manager.
* <p/>
* <p>Temp file managers are created 1-to-1 with incoming requests, to create and cleanup
* temporary files created as a result of handling the request.</p>
*/
public interface TempFileManager
{
TempFile createTempFile() throws Exception;
void clear();
}
/**
* A temp file.
* <p/>
* <p>Temp files are responsible for managing the actual temporary storage and cleaning
* themselves up when no longer needed.</p>
*/
public interface TempFile
{
OutputStream open() throws Exception;
void delete() throws Exception;
String getName();
}
/** /**
* Default threading strategy for NanoHttpd. * Default threading strategy for NanoHttpd.
* * <p/>
* <p>By default, the server spawns a new Thread for every incoming request. These are set * <p>By default, the server spawns a new Thread for every incoming request. These are set
* to <i>daemon</i> status, and named according to the request number. The name is * to <i>daemon</i> status, and named according to the request number. The name is
* useful when profiling the application.</p> * useful when profiling the application.</p>
@ -367,76 +477,10 @@ public abstract class NanoHTTPD
t.start(); t.start();
} }
} }
// ------------------------------------------------------------------------------- //
//
// Temp file handling strategy.
//
// ------------------------------------------------------------------------------- //
/**
* Pluggable strategy for creating and cleaning up temporary files.
*/
private TempFileManagerFactory tempFileManagerFactory;
/**
* Pluggable strategy for creating and cleaning up temporary files.
* @param tempFileManagerFactory new strategy for handling temp files.
*/
public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory)
{
this.tempFileManagerFactory = tempFileManagerFactory;
}
/**
* Factory to create temp file managers.
*/
public interface TempFileManagerFactory
{
TempFileManager create();
}
/**
* Temp file manager.
*
* <p>Temp file managers are created 1-to-1 with incoming requests, to create and cleanup
* temporary files created as a result of handling the request.</p>
*/
public interface TempFileManager
{
TempFile createTempFile() throws Exception;
void clear();
}
/**
* A temp file.
*
* <p>Temp files are responsible for managing the actual temporary storage and cleaning
* themselves up when no longer needed.</p>
*/
public interface TempFile
{
OutputStream open() throws Exception;
void delete() throws Exception;
String getName();
}
/** /**
* Default strategy for creating and cleaning up temporary files. * Default strategy for creating and cleaning up temporary files.
*/ * <p/>
private class DefaultTempFileManagerFactory implements TempFileManagerFactory
{
@Override
public TempFileManager create()
{
return new DefaultTempFileManager();
}
}
/**
* Default strategy for creating and cleaning up temporary files.
*
* <p></p>This class stores its files in the standard location (that is, * <p></p>This class stores its files in the standard location (that is,
* wherever <code>java.io.tmpdir</code> points to). Files are added * wherever <code>java.io.tmpdir</code> points to). Files are added
* to an internal list, and deleted when no longer needed (that is, * to an internal list, and deleted when no longer needed (that is,
@ -481,7 +525,7 @@ public abstract class NanoHTTPD
/** /**
* Default strategy for creating and cleaning up temporary files. * Default strategy for creating and cleaning up temporary files.
* * <p/>
* <p></p></[>By default, files are created by <code>File.createTempFile()</code> in * <p></p></[>By default, files are created by <code>File.createTempFile()</code> in
* the directory specified.</p> * the directory specified.</p>
*/ */
@ -516,7 +560,6 @@ public abstract class NanoHTTPD
} }
} }
// ------------------------------------------------------------------------------- //
/** /**
* HTTP response. Return one of these from serve(). * HTTP response. Return one of these from serve().
*/ */
@ -542,6 +585,10 @@ public abstract class NanoHTTPD
* The request method that spawned this response. * The request method that spawned this response.
*/ */
private Method requestMethod; private Method requestMethod;
/**
* Use chunkedTransfer
*/
private boolean chunkedTransfer;
/** /**
* Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message
@ -623,31 +670,15 @@ public abstract class NanoHTTPD
} }
} }
int pending = data != null ? data.available() : -1; // This is to support partial sends, see serveFile() pw.print("Connection: keep-alive\r\n");
if (pending > 0)
if (requestMethod != Method.HEAD && chunkedTransfer)
{ {
pw.print("Connection: keep-alive\r\n"); sendAsChunked(outputStream, pw);
pw.print("Content-Length: " + pending + "\r\n");
} }
else
pw.print("\r\n");
pw.flush();
if (requestMethod != Method.HEAD && data != null)
{ {
int BUFFER_SIZE = 16 * 1024; sendAsFixedLength(outputStream, pw);
byte[] buff = new byte[BUFFER_SIZE];
while (pending > 0)
{
int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
if (read <= 0)
{
break;
}
outputStream.write(buff, 0, read);
pending -= read;
}
} }
outputStream.flush(); outputStream.flush();
safeClose(data); safeClose(data);
@ -658,6 +689,50 @@ public abstract class NanoHTTPD
} }
} }
private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException
{
pw.print("Transfer-Encoding: chunked\r\n");
pw.print("\r\n");
pw.flush();
int BUFFER_SIZE = 16 * 1024;
byte[] CRLF = "\r\n".getBytes();
byte[] buff = new byte[BUFFER_SIZE];
int read;
while ((read = data.read(buff)) > 0)
{
outputStream.write(String.format("%x\r\n", read).getBytes());
outputStream.write(buff, 0, read);
outputStream.write(CRLF);
}
outputStream.write(String.format("0\r\n\r\n").getBytes());
}
private void sendAsFixedLength(OutputStream outputStream, PrintWriter pw) throws IOException
{
int pending = data != null ? data.available() : 0; // This is to support partial sends, see serveFile()
pw.print("Content-Length: " + pending + "\r\n");
pw.print("\r\n");
pw.flush();
if (requestMethod != Method.HEAD && data != null)
{
int BUFFER_SIZE = 16 * 1024;
byte[] buff = new byte[BUFFER_SIZE];
while (pending > 0)
{
int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
if (read <= 0)
{
break;
}
outputStream.write(buff, 0, read);
pending -= read;
}
}
}
public Status getStatus() public Status getStatus()
{ {
return status; return status;
@ -698,6 +773,11 @@ public abstract class NanoHTTPD
this.requestMethod = requestMethod; this.requestMethod = requestMethod;
} }
public void setChunkedTransfer(boolean chunkedTransfer)
{
this.chunkedTransfer = chunkedTransfer;
}
/** /**
* Some HTTP response status codes * Some HTTP response status codes
*/ */
@ -728,6 +808,40 @@ public abstract class NanoHTTPD
} }
} }
public static final class ResponseException extends Exception
{
private final Response.Status status;
public ResponseException(Response.Status status, String message)
{
super(message);
this.status = status;
}
public ResponseException(Response.Status status, String message, Exception e)
{
super(message, e);
this.status = status;
}
public Response.Status getStatus()
{
return status;
}
}
/**
* Default strategy for creating and cleaning up temporary files.
*/
private class DefaultTempFileManagerFactory implements TempFileManagerFactory
{
@Override
public TempFileManager create()
{
return new DefaultTempFileManager();
}
}
/** /**
* Handles one session, i.e. parses the HTTP request and returns the response. * Handles one session, i.e. parses the HTTP request and returns the response.
*/ */
@ -735,17 +849,18 @@ public abstract class NanoHTTPD
{ {
public static final int BUFSIZE = 8192; public static final int BUFSIZE = 8192;
private final TempFileManager tempFileManager; private final TempFileManager tempFileManager;
private InputStream inputStream;
private final OutputStream outputStream; private final OutputStream outputStream;
private final Socket socket;
private InputStream inputStream;
private int splitbyte; private int splitbyte;
private int rlen; private int rlen;
private String uri; private String uri;
private Method method; private Method method;
private Map<String, String> parms; private Map<String, String> parms;
private Map<String, String> headers; private Map<String, String> headers;
private Socket socket; private CookieHandler cookies;
private HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, Socket socket) public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, Socket socket)
{ {
this.tempFileManager = tempFileManager; this.tempFileManager = tempFileManager;
this.inputStream = inputStream; this.inputStream = inputStream;
@ -808,6 +923,8 @@ public abstract class NanoHTTPD
uri = pre.get("uri"); uri = pre.get("uri");
cookies = new CookieHandler(headers);
// Ok, now do the serve() // Ok, now do the serve()
Response r = serve(this); Response r = serve(this);
if (r == null) if (r == null)
@ -816,6 +933,7 @@ public abstract class NanoHTTPD
} }
else else
{ {
cookies.unloadQueue(r);
r.setRequestMethod(method); r.setRequestMethod(method);
r.send(outputStream); r.send(outputStream);
} }
@ -843,7 +961,7 @@ public abstract class NanoHTTPD
} }
} }
private void parseBody(Map<String, String> files) throws IOException, ResponseException protected void parseBody(Map<String, String> files) throws IOException, ResponseException
{ {
RandomAccessFile randomAccessFile = null; RandomAccessFile randomAccessFile = null;
BufferedReader in = null; BufferedReader in = null;
@ -914,7 +1032,7 @@ public abstract class NanoHTTPD
String boundaryStartString = "boundary="; String boundaryStartString = "boundary=";
int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length(); int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length()); String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
if (boundary.startsWith("\"") && boundary.startsWith("\"")) if (boundary.startsWith("\"") && boundary.endsWith("\""))
{ {
boundary = boundary.substring(1, boundary.length() - 1); boundary = boundary.substring(1, boundary.length() - 1);
} }
@ -1189,7 +1307,7 @@ public abstract class NanoHTTPD
path = tempFile.getName(); path = tempFile.getName();
} }
catch (Exception e) catch (Exception e)
{ { // Catch exception if any
TFM_Log.severe(e); TFM_Log.severe(e);
} }
finally finally
@ -1285,88 +1403,140 @@ public abstract class NanoHTTPD
return inputStream; return inputStream;
} }
public final Socket getSocket() public CookieHandler getCookies()
{
return cookies;
}
public Socket getSocket()
{ {
return socket; return socket;
} }
} }
private static final class ResponseException extends Exception public static class Cookie
{ {
private final Response.Status status; private String n, v, e;
public ResponseException(Response.Status status, String message) public Cookie(String name, String value, String expires)
{ {
super(message); n = name;
this.status = status; v = value;
e = expires;
} }
public ResponseException(Response.Status status, String message, Exception e) public Cookie(String name, String value)
{ {
super(message, e); this(name, value, 30);
this.status = status;
} }
public Response.Status getStatus() public Cookie(String name, String value, int numDays)
{ {
return status; n = name;
v = value;
e = getHTTPTime(numDays);
}
public String getHTTPHeader()
{
String fmt = "%s=%s; expires=%s";
return String.format(fmt, n, v, e);
}
public static String getHTTPTime(int days)
{
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
calendar.add(Calendar.DAY_OF_MONTH, days);
return dateFormat.format(calendar.getTime());
} }
} }
private static final void safeClose(ServerSocket serverSocket) /**
* Provides rudimentary support for cookies.
* Doesn't support 'path', 'secure' nor 'httpOnly'.
* Feel free to improve it and/or add unsupported features.
*
* @author LordFokas
*/
public class CookieHandler implements Iterable<String>
{ {
if (serverSocket != null) private HashMap<String, String> cookies = new HashMap<String, String>();
private ArrayList<Cookie> queue = new ArrayList<Cookie>();
public CookieHandler(Map<String, String> httpHeaders)
{ {
try String raw = httpHeaders.get("cookie");
if (raw != null)
{ {
serverSocket.close(); String[] tokens = raw.split(";");
for (String token : tokens)
{
String[] data = token.trim().split("=");
if (data.length == 2)
{
cookies.put(data[0], data[1]);
}
}
} }
catch (IOException e) }
@Override
public Iterator<String> iterator()
{
return cookies.keySet().iterator();
}
/**
* Read a cookie from the HTTP Headers.
*
* @param name The cookie's name.
* @return The cookie's value if it exists, null otherwise.
*/
public String read(String name)
{
return cookies.get(name);
}
/**
* Sets a cookie.
*
* @param name The cookie's name.
* @param value The cookie's value.
* @param expires How many days until the cookie expires.
*/
public void set(String name, String value, int expires)
{
queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));
}
public void set(Cookie cookie)
{
queue.add(cookie);
}
/**
* Set a cookie with an expiration date from a month ago, effectively deleting it on the client side.
*
* @param name The cookie name.
*/
public void delete(String name)
{
set(name, "-delete-", -30);
}
/**
* Internally used by the webserver to add all queued cookies into the Response's HTTP Headers.
*
* @param response The Response object to which headers the queued cookies will be added.
*/
public void unloadQueue(Response response)
{
for (Cookie cookie : queue)
{ {
response.addHeader("Set-Cookie", cookie.getHTTPHeader());
} }
} }
} }
}
private static final void safeClose(Socket socket)
{
if (socket != null)
{
try
{
socket.close();
}
catch (IOException e)
{
}
}
}
private static final void safeClose(Closeable closeable)
{
if (closeable != null)
{
try
{
closeable.close();
}
catch (IOException e)
{
}
}
}
public final int getListeningPort()
{
return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
}
public final boolean wasStarted()
{
return myServerSocket != null && myThread != null;
}
public final boolean isAlive()
{
return wasStarted() && !myServerSocket.isClosed() && myThread.isAlive();
}
}

View file

@ -3,12 +3,10 @@ package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.HTTPSession;
import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.Response; import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.Response;
import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry; import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry;
import me.StevenLawson.TotalFreedomMod.TFM_Log; import me.StevenLawson.TotalFreedomMod.TFM_Log;
@ -19,6 +17,9 @@ import org.bukkit.Bukkit;
public class TFM_HTTPD_Manager public class TFM_HTTPD_Manager
{ {
@Deprecated
public static String MIME_DEFAULT_BINARY = "application/octet-stream";
//
private static final Pattern EXT_REGEX = Pattern.compile("\\.([^\\.\\s]+)$"); private static final Pattern EXT_REGEX = Pattern.compile("\\.([^\\.\\s]+)$");
// //
public static final int PORT = TFM_ConfigEntry.HTTPD_PORT.getInteger(); public static final int PORT = TFM_ConfigEntry.HTTPD_PORT.getInteger();
@ -69,36 +70,118 @@ public class TFM_HTTPD_Manager
private static enum ModuleType private static enum ModuleType
{ {
DUMP(false, "dump"), DUMP(new ModuleExecutable(false, "dump")
HELP(true, "help"),
LIST(true, "list"),
FILE(false, "file"),
SCHEMATIC(false, "schematic"),
PERMBANS(false, "permbans");
private final boolean runOnBukkitThread;
private final String name;
private ModuleType(boolean runOnBukkitThread, String name)
{ {
this.runOnBukkitThread = runOnBukkitThread; @Override
this.name = name; public Response getResponse(HTTPSession session)
{
return new Response(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, "The DUMP module is disabled. It is intended for debugging use only.");
}
}),
HELP(new ModuleExecutable(true, "help")
{
@Override
public Response getResponse(HTTPSession session)
{
return new Module_help(session).getResponse();
}
}),
LIST(new ModuleExecutable(true, "list")
{
@Override
public Response getResponse(HTTPSession session)
{
return new Module_list(session).getResponse();
}
}),
FILE(new ModuleExecutable(false, "file")
{
@Override
public Response getResponse(HTTPSession session)
{
return new Module_file(session).getResponse();
}
}),
SCHEMATIC(new ModuleExecutable(false, "schematic")
{
@Override
public Response getResponse(HTTPSession session)
{
return new Module_schematic(session).getResponse();
}
}),
PERMBANS(new ModuleExecutable(false, "permbans")
{
@Override
public Response getResponse(HTTPSession session)
{
return new Module_permbans(session).getResponse();
}
});
//
private final ModuleExecutable moduleExecutable;
private ModuleType(ModuleExecutable moduleExecutable)
{
this.moduleExecutable = moduleExecutable;
} }
public boolean isRunOnBukkitThread() private abstract static class ModuleExecutable
{ {
return runOnBukkitThread; private final boolean runOnBukkitThread;
private final String name;
public ModuleExecutable(boolean runOnBukkitThread, String name)
{
this.runOnBukkitThread = runOnBukkitThread;
this.name = name;
}
public Response execute(final HTTPSession session)
{
try
{
if (this.runOnBukkitThread)
{
return Bukkit.getScheduler().callSyncMethod(TotalFreedomMod.plugin, new Callable<Response>()
{
@Override
public Response call() throws Exception
{
return getResponse(session);
}
}).get();
}
else
{
return getResponse(session);
}
}
catch (Exception ex)
{
TFM_Log.severe(ex);
}
return null;
}
public abstract Response getResponse(HTTPSession session);
public String getName()
{
return name;
}
} }
public String getName() public ModuleExecutable getModuleExecutable()
{ {
return name; return moduleExecutable;
} }
private static ModuleType getByName(String needle) private static ModuleType getByName(String needle)
{ {
for (ModuleType type : values()) for (ModuleType type : values())
{ {
if (type.getName().equalsIgnoreCase(needle)) if (type.getModuleExecutable().getName().equalsIgnoreCase(needle))
{ {
return type; return type;
} }
@ -120,68 +203,15 @@ public class TFM_HTTPD_Manager
} }
@Override @Override
public Response serve( public Response serve(HTTPSession session)
final String uri,
final Method method,
final Map<String, String> headers,
final Map<String, String> params,
final Map<String, String> files,
final Socket socket)
{ {
Response response = null; Response response;
try try
{ {
final String[] args = StringUtils.split(session.getUri(), "/");
final String[] args = StringUtils.split(uri, "/");
final ModuleType moduleType = args.length >= 1 ? ModuleType.getByName(args[0]) : ModuleType.FILE; final ModuleType moduleType = args.length >= 1 ? ModuleType.getByName(args[0]) : ModuleType.FILE;
response = moduleType.getModuleExecutable().execute(session);
if (moduleType.isRunOnBukkitThread())
{
Future<Response> responseCall = Bukkit.getScheduler().callSyncMethod(TotalFreedomMod.plugin, new Callable<Response>()
{
@Override
public Response call() throws Exception
{
switch (moduleType)
{
case HELP:
return new Module_help(uri, method, headers, params, files, socket).getResponse();
case LIST:
return new Module_list(uri, method, headers, params, files, socket).getResponse();
default:
return null;
}
}
});
try
{
response = responseCall.get();
}
catch (Exception ex)
{
TFM_Log.severe(ex);
}
}
else
{
switch (moduleType)
{
case DUMP:
//response = new Module_dump(uri, method, headers, params, files, socket).getResponse();
response = new Response(Response.Status.OK, MIME_PLAINTEXT, "The DUMP module is disabled. It is intended for debugging use only.");
break;
case SCHEMATIC:
response = new Module_schematic(uri, method, headers, params, files, socket).getResponse();
break;
case PERMBANS:
response = new Module_permbans(uri, method, headers, params, files, socket).getResponse();
break;
default:
response = new Module_file(uri, method, headers, params, files, socket).getResponse();
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -215,10 +245,10 @@ public class TFM_HTTPD_Manager
if (mimetype == null || mimetype.trim().isEmpty()) if (mimetype == null || mimetype.trim().isEmpty())
{ {
mimetype = NanoHTTPD.MIME_DEFAULT_BINARY; mimetype = MIME_DEFAULT_BINARY;
} }
response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, mimetype, new FileInputStream(file)); response = new Response(Response.Status.OK, mimetype, new FileInputStream(file));
response.addHeader("Content-Length", "" + file.length()); response.addHeader("Content-Length", "" + file.length());
} }
catch (IOException ex) catch (IOException ex)

View file

@ -1,8 +1,10 @@
package me.StevenLawson.TotalFreedomMod.HTTPD; package me.StevenLawson.TotalFreedomMod.HTTPD;
import java.net.Socket; import java.net.Socket;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.*; import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.*;
import me.StevenLawson.TotalFreedomMod.TFM_Log;
public abstract class TFM_HTTPD_Module public abstract class TFM_HTTPD_Module
{ {
@ -10,17 +12,17 @@ public abstract class TFM_HTTPD_Module
protected final Method method; protected final Method method;
protected final Map<String, String> headers; protected final Map<String, String> headers;
protected final Map<String, String> params; protected final Map<String, String> params;
protected final Map<String, String> files;
protected final Socket socket; protected final Socket socket;
protected final HTTPSession session;
public TFM_HTTPD_Module(String uri, Method method, Map<String, String> headers, Map<String, String> params, Map<String, String> files, Socket socket) public TFM_HTTPD_Module(HTTPSession session)
{ {
this.uri = uri; this.uri = session.getUri();
this.method = method; this.method = session.getMethod();
this.headers = headers; this.headers = session.getHeaders();
this.params = params; this.params = session.getParms();
this.files = files; this.socket = session.getSocket();
this.socket = socket; this.session = session;
} }
public String getBody() public String getBody()
@ -47,4 +49,20 @@ public abstract class TFM_HTTPD_Module
{ {
return new TFM_HTTPD_PageBuilder(getBody(), getTitle(), getStyle(), getScript()).getResponse(); return new TFM_HTTPD_PageBuilder(getBody(), getTitle(), getStyle(), getScript()).getResponse();
} }
protected final Map<String, String> getFiles()
{
Map<String, String> files = new HashMap<String, String>();
try
{
session.parseBody(files);
}
catch (Exception ex)
{
TFM_Log.severe(ex);
}
return files;
}
} }