From e10ab45bda426ffad72964c19794a987424a026d Mon Sep 17 00:00:00 2001 From: StevenLawson Date: Tue, 17 Sep 2013 21:31:46 -0400 Subject: [PATCH] Update NanoHTTPd to https://github.com/NanoHttpd/nanohttpd/commit/12b4973a5265890754904c75f5eeb6ef1b29373a --- appinfo.properties | 6 +- buildnumber.properties | 4 +- .../TotalFreedomMod/HTTPD/Module_dump.java | 7 +- .../TotalFreedomMod/HTTPD/Module_file.java | 9 +- .../TotalFreedomMod/HTTPD/Module_help.java | 5 +- .../TotalFreedomMod/HTTPD/Module_list.java | 6 +- .../HTTPD/Module_permbans.java | 6 +- .../HTTPD/Module_schematic.java | 9 +- .../TotalFreedomMod/HTTPD/NanoHTTPD.java | 590 +++++++++++------- .../HTTPD/TFM_HTTPD_Manager.java | 188 +++--- .../HTTPD/TFM_HTTPD_Module.java | 34 +- 11 files changed, 539 insertions(+), 325 deletions(-) diff --git a/appinfo.properties b/appinfo.properties index 75b7f287..b9846568 100644 --- a/appinfo.properties +++ b/appinfo.properties @@ -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.BUILDNUM=595 -program.BUILDDATE=09/17/2013 12\:03 PM +program.BUILDNUM=596 +program.BUILDDATE=09/17/2013 09\:28 PM diff --git a/buildnumber.properties b/buildnumber.properties index 08137238..58a94e91 100644 --- a/buildnumber.properties +++ b/buildnumber.properties @@ -1,3 +1,3 @@ #Build Number for ANT. Do not edit! -#Tue Sep 17 12:03:45 EDT 2013 -build.number=596 +#Tue Sep 17 21:28:11 EDT 2013 +build.number=597 diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_dump.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_dump.java index ce6e43e2..7e86c79b 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_dump.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_dump.java @@ -2,7 +2,6 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.io.File; import java.io.IOException; -import java.net.Socket; import java.util.Iterator; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -16,9 +15,9 @@ public class Module_dump extends TFM_HTTPD_Module private File echoFile = null; private final String body; - public Module_dump(String uri, NanoHTTPD.Method method, Map headers, Map params, Map 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. this.body = body(); @@ -54,6 +53,8 @@ public class Module_dump extends TFM_HTTPD_Module String[] args = StringUtils.split(uri, "/"); + Map files = getFiles(); + responseBody .append(paragraph("URI: " + uri)) .append(paragraph("args (Length: " + args.length + "): " + StringUtils.join(args, ","))) diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_file.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_file.java index 0470a14b..f241a922 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_file.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_file.java @@ -5,7 +5,6 @@ import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.Socket; import java.net.URLEncoder; import java.util.Arrays; import java.util.Collections; @@ -13,9 +12,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry; 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 @@ -55,9 +54,9 @@ public class Module_file extends TFM_HTTPD_Module MIME_TYPES.put("class", "application/octet-stream"); } - public Module_file(String uri, Method method, Map headers, Map params, Map files, Socket socket) + public Module_file(NanoHTTPD.HTTPSession session) { - super(uri, method, headers, params, files, socket); + super(session); } private File getRootDir() @@ -175,7 +174,7 @@ public class Module_file extends TFM_HTTPD_Module } if (mime == null) { - mime = NanoHTTPD.MIME_DEFAULT_BINARY; + mime = TFM_HTTPD_Manager.MIME_DEFAULT_BINARY; } // Calculate etag diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_help.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_help.java index f848300d..ba6f5483 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_help.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_help.java @@ -1,6 +1,5 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; -import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -21,9 +20,9 @@ import static org.apache.commons.lang3.StringEscapeUtils.*; public class Module_help extends TFM_HTTPD_Module { - public Module_help(String uri, NanoHTTPD.Method method, Map headers, Map params, Map files, Socket socket) + public Module_help(NanoHTTPD.HTTPSession session) { - super(uri, method, headers, params, files, socket); + super(session); } @Override diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_list.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_list.java index 4db7282f..e1eb806a 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_list.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_list.java @@ -1,7 +1,5 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; -import java.net.Socket; -import java.util.Map; import me.StevenLawson.TotalFreedomMod.TFM_SuperadminList; import me.StevenLawson.TotalFreedomMod.TFM_Util; import org.bukkit.Bukkit; @@ -9,9 +7,9 @@ import org.bukkit.entity.Player; public class Module_list extends TFM_HTTPD_Module { - public Module_list(String uri, NanoHTTPD.Method method, Map headers, Map params, Map files, Socket socket) + public Module_list(NanoHTTPD.HTTPSession session) { - super(uri, method, headers, params, files, socket); + super(session); } @Override diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_permbans.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_permbans.java index f261ffee..658f08f8 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_permbans.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_permbans.java @@ -1,15 +1,13 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.io.File; -import java.net.Socket; -import java.util.Map; import me.StevenLawson.TotalFreedomMod.TotalFreedomMod; public class Module_permbans extends TFM_HTTPD_Module { - public Module_permbans(String uri, NanoHTTPD.Method method, Map headers, Map params, Map files, Socket socket) + public Module_permbans(NanoHTTPD.HTTPSession session) { - super(uri, method, headers, params, files, socket); + super(session); } @Override diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_schematic.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_schematic.java index 1805e2b8..d5cab3c9 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_schematic.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/Module_schematic.java @@ -2,7 +2,6 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.io.File; import java.io.IOException; -import java.net.Socket; import java.util.ArrayList; import java.util.Collection; 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_SuperadminList; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; public class Module_schematic extends TFM_HTTPD_Module { @@ -36,9 +35,9 @@ public class Module_schematic extends TFM_HTTPD_Module + "\n" + ""; - public Module_schematic(String uri, Method method, Map headers, Map params, Map files, Socket socket) + public Module_schematic(NanoHTTPD.HTTPSession session) { - super(uri, method, headers, params, files, socket); + super(session); } @Override @@ -153,6 +152,8 @@ public class Module_schematic extends TFM_HTTPD_Module private boolean uploadSchematic() throws SchematicTransferException { + Map files = getFiles(); + final String tempFileName = files.get(REQUEST_FORM_FILE_ELEMENT_NAME); if (tempFileName == null) { diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/NanoHTTPD.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/NanoHTTPD.java index ac7875c3..8ace0bdc 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/NanoHTTPD.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/NanoHTTPD.java @@ -1,11 +1,7 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.io.*; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.URLDecoder; +import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.text.SimpleDateFormat; @@ -67,17 +63,21 @@ public abstract class NanoHTTPD */ 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 int myPort; private ServerSocket myServerSocket; 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. @@ -98,8 +98,51 @@ public abstract class NanoHTTPD 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. + * * @throws IOException if the socket is in use. */ 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. *

*

* (By default, this delegates to serveFile() and allows directory listing.) * - * @param uri Percent-decoded URI without parameters, for example "/index.cgi" - * @param method "GET", "POST" etc. - * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. + * @param uri Percent-decoded URI without parameters, for example "/index.cgi" + * @param method "GET", "POST" etc. + * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. * @param headers Header entries, percent decoded * @return HTTP response, see class Response for details */ - public abstract Response serve(String uri, Method method, Map headers, Map parms, - Map files, Socket socket); + @Deprecated + public Response serve(String uri, Method method, Map headers, Map parms, + Map files) + { + return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found"); + } /** * Override this to customize the server. @@ -211,33 +273,32 @@ public abstract class NanoHTTPD * @param session The HTTP session * @return HTTP response, see class Response for details */ - protected Response serve(HTTPSession session) + public Response serve(HTTPSession session) { Map files = new HashMap(); - - 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(); - Map parms = session.getParms(); - Map headers = session.getHeaders(); - Socket socket = session.getSocket(); - return serve(uri, method, headers, parms, files, socket); + if (Method.PUT.equals(method) || Method.POST.equals(method)) + { + 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()); + } + } + + return serve(session.getUri(), method, session.getHeaders(), session.getParms(), files); } /** * Decode percent encoded String values. + * * @param str the percent encoded String * @return expanded form of the input, for example "foo%20bar" becomes "foo bar" */ @@ -300,6 +361,36 @@ public abstract class NanoHTTPD 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 String back to its enum value. */ @@ -319,24 +410,6 @@ public abstract class NanoHTTPD 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. @@ -346,9 +419,46 @@ public abstract class NanoHTTPD void exec(Runnable code); } + /** + * Factory to create temp file managers. + */ + public interface TempFileManagerFactory + { + TempFileManager create(); + } + + // ------------------------------------------------------------------------------- // + /** + * Temp file manager. + *

+ *

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.

+ */ + public interface TempFileManager + { + TempFile createTempFile() throws Exception; + + void clear(); + } + + /** + * A temp file. + *

+ *

Temp files are responsible for managing the actual temporary storage and cleaning + * themselves up when no longer needed.

+ */ + public interface TempFile + { + OutputStream open() throws Exception; + + void delete() throws Exception; + + String getName(); + } + /** * Default threading strategy for NanoHttpd. - * + *

*

By default, the server spawns a new Thread for every incoming request. These are set * to daemon status, and named according to the request number. The name is * useful when profiling the application.

@@ -367,76 +477,10 @@ public abstract class NanoHTTPD 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. - * - *

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.

- */ - public interface TempFileManager - { - TempFile createTempFile() throws Exception; - - void clear(); - } - - /** - * A temp file. - * - *

Temp files are responsible for managing the actual temporary storage and cleaning - * themselves up when no longer needed.

- */ - public interface TempFile - { - OutputStream open() throws Exception; - - void delete() throws Exception; - - String getName(); - } /** * Default strategy for creating and cleaning up temporary files. - */ - private class DefaultTempFileManagerFactory implements TempFileManagerFactory - { - @Override - public TempFileManager create() - { - return new DefaultTempFileManager(); - } - } - - /** - * Default strategy for creating and cleaning up temporary files. - * + *

*

This class stores its files in the standard location (that is, * wherever java.io.tmpdir points to). Files are added * 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. - * + *

*

By default, files are created by File.createTempFile() in * the directory specified.

*/ @@ -516,7 +560,6 @@ public abstract class NanoHTTPD } } - // ------------------------------------------------------------------------------- // /** * HTTP response. Return one of these from serve(). */ @@ -542,6 +585,10 @@ public abstract class NanoHTTPD * The request method that spawned this response. */ private Method requestMethod; + /** + * Use chunkedTransfer + */ + private boolean chunkedTransfer; /** * 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() - if (pending > 0) + pw.print("Connection: keep-alive\r\n"); + + if (requestMethod != Method.HEAD && chunkedTransfer) { - pw.print("Connection: keep-alive\r\n"); - pw.print("Content-Length: " + pending + "\r\n"); + sendAsChunked(outputStream, pw); } - - pw.print("\r\n"); - pw.flush(); - - if (requestMethod != Method.HEAD && data != null) + else { - 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; - } + sendAsFixedLength(outputStream, pw); } outputStream.flush(); 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() { return status; @@ -698,6 +773,11 @@ public abstract class NanoHTTPD this.requestMethod = requestMethod; } + public void setChunkedTransfer(boolean chunkedTransfer) + { + this.chunkedTransfer = chunkedTransfer; + } + /** * 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. */ @@ -735,17 +849,18 @@ public abstract class NanoHTTPD { public static final int BUFSIZE = 8192; private final TempFileManager tempFileManager; - private InputStream inputStream; private final OutputStream outputStream; + private final Socket socket; + private InputStream inputStream; private int splitbyte; private int rlen; private String uri; private Method method; private Map parms; private Map 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.inputStream = inputStream; @@ -808,6 +923,8 @@ public abstract class NanoHTTPD uri = pre.get("uri"); + cookies = new CookieHandler(headers); + // Ok, now do the serve() Response r = serve(this); if (r == null) @@ -816,6 +933,7 @@ public abstract class NanoHTTPD } else { + cookies.unloadQueue(r); r.setRequestMethod(method); r.send(outputStream); } @@ -843,7 +961,7 @@ public abstract class NanoHTTPD } } - private void parseBody(Map files) throws IOException, ResponseException + protected void parseBody(Map files) throws IOException, ResponseException { RandomAccessFile randomAccessFile = null; BufferedReader in = null; @@ -914,7 +1032,7 @@ public abstract class NanoHTTPD String boundaryStartString = "boundary="; int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.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); } @@ -1189,7 +1307,7 @@ public abstract class NanoHTTPD path = tempFile.getName(); } catch (Exception e) - { + { // Catch exception if any TFM_Log.severe(e); } finally @@ -1285,88 +1403,140 @@ public abstract class NanoHTTPD return inputStream; } - public final Socket getSocket() + public CookieHandler getCookies() + { + return cookies; + } + + public Socket getSocket() { 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); - this.status = status; + n = name; + v = value; + e = expires; } - public ResponseException(Response.Status status, String message, Exception e) + public Cookie(String name, String value) { - super(message, e); - this.status = status; + this(name, value, 30); } - 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 { - if (serverSocket != null) + private HashMap cookies = new HashMap(); + private ArrayList queue = new ArrayList(); + + public CookieHandler(Map 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 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(); - } -} \ No newline at end of file +} diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Manager.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Manager.java index 58826045..8966d573 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Manager.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Manager.java @@ -3,12 +3,10 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.net.Socket; -import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; +import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.HTTPSession; import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.Response; import me.StevenLawson.TotalFreedomMod.TFM_ConfigEntry; import me.StevenLawson.TotalFreedomMod.TFM_Log; @@ -19,6 +17,9 @@ import org.bukkit.Bukkit; public class TFM_HTTPD_Manager { + @Deprecated + public static String MIME_DEFAULT_BINARY = "application/octet-stream"; + // private static final Pattern EXT_REGEX = Pattern.compile("\\.([^\\.\\s]+)$"); // public static final int PORT = TFM_ConfigEntry.HTTPD_PORT.getInteger(); @@ -69,36 +70,118 @@ public class TFM_HTTPD_Manager private static enum ModuleType { - DUMP(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) + DUMP(new ModuleExecutable(false, "dump") { - this.runOnBukkitThread = runOnBukkitThread; - this.name = name; + @Override + 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() + { + @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) { for (ModuleType type : values()) { - if (type.getName().equalsIgnoreCase(needle)) + if (type.getModuleExecutable().getName().equalsIgnoreCase(needle)) { return type; } @@ -120,68 +203,15 @@ public class TFM_HTTPD_Manager } @Override - public Response serve( - final String uri, - final Method method, - final Map headers, - final Map params, - final Map files, - final Socket socket) + public Response serve(HTTPSession session) { - Response response = null; + Response response; try { - - final String[] args = StringUtils.split(uri, "/"); + final String[] args = StringUtils.split(session.getUri(), "/"); final ModuleType moduleType = args.length >= 1 ? ModuleType.getByName(args[0]) : ModuleType.FILE; - - if (moduleType.isRunOnBukkitThread()) - { - Future responseCall = Bukkit.getScheduler().callSyncMethod(TotalFreedomMod.plugin, new Callable() - { - @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(); - } - } + response = moduleType.getModuleExecutable().execute(session); } catch (Exception ex) { @@ -215,10 +245,10 @@ public class TFM_HTTPD_Manager 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()); } catch (IOException ex) diff --git a/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Module.java b/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Module.java index a6aa4b0d..98688d7a 100644 --- a/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Module.java +++ b/src/me/StevenLawson/TotalFreedomMod/HTTPD/TFM_HTTPD_Module.java @@ -1,8 +1,10 @@ package me.StevenLawson.TotalFreedomMod.HTTPD; import java.net.Socket; +import java.util.HashMap; import java.util.Map; import me.StevenLawson.TotalFreedomMod.HTTPD.NanoHTTPD.*; +import me.StevenLawson.TotalFreedomMod.TFM_Log; public abstract class TFM_HTTPD_Module { @@ -10,17 +12,17 @@ public abstract class TFM_HTTPD_Module protected final Method method; protected final Map headers; protected final Map params; - protected final Map files; protected final Socket socket; + protected final HTTPSession session; - public TFM_HTTPD_Module(String uri, Method method, Map headers, Map params, Map files, Socket socket) + public TFM_HTTPD_Module(HTTPSession session) { - this.uri = uri; - this.method = method; - this.headers = headers; - this.params = params; - this.files = files; - this.socket = socket; + this.uri = session.getUri(); + this.method = session.getMethod(); + this.headers = session.getHeaders(); + this.params = session.getParms(); + this.socket = session.getSocket(); + this.session = session; } public String getBody() @@ -47,4 +49,20 @@ public abstract class TFM_HTTPD_Module { return new TFM_HTTPD_PageBuilder(getBody(), getTitle(), getStyle(), getScript()).getResponse(); } + + protected final Map getFiles() + { + Map files = new HashMap(); + + try + { + session.parseBody(files); + } + catch (Exception ex) + { + TFM_Log.severe(ex); + } + + return files; + } }