mirror of
https://github.com/AtlasMediaGroup/TotalFreedomMod.git
synced 2025-06-11 13:33:54 +00:00
Mavenized project
This commit is contained in:
@ -0,0 +1,58 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
|
||||
|
||||
public class HTMLGenerationTools
|
||||
{
|
||||
private HTMLGenerationTools()
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static String paragraph(String data)
|
||||
{
|
||||
return "<p>" + escapeHtml4(data) + "</p>\r\n";
|
||||
}
|
||||
|
||||
public static String heading(String data, int level)
|
||||
{
|
||||
return "<h" + level + ">" + escapeHtml4(data) + "</h" + level + ">\r\n";
|
||||
}
|
||||
|
||||
public static <K, V> String list(Map<K, V> map)
|
||||
{
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
output.append("<ul>\r\n");
|
||||
|
||||
Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Map.Entry<K, V> entry = it.next();
|
||||
output.append("<li>").append(escapeHtml4(entry.getKey().toString() + " = " + entry.getValue().toString())).append("</li>\r\n");
|
||||
}
|
||||
|
||||
output.append("</ul>\r\n");
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
public static <T> String list(Collection<T> list)
|
||||
{
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
output.append("<ul>\r\n");
|
||||
|
||||
for (T entry : list)
|
||||
{
|
||||
output.append("<li>").append(escapeHtml4(entry.toString())).append("</li>\r\n");
|
||||
}
|
||||
|
||||
output.append("</ul>\r\n");
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.HTTPSession;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Method;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Response;
|
||||
import me.totalfreedom.totalfreedommod.util.FLog;
|
||||
|
||||
public abstract class HTTPDModule
|
||||
{
|
||||
protected final String uri;
|
||||
protected final Method method;
|
||||
protected final Map<String, String> headers;
|
||||
protected final Map<String, String> params;
|
||||
protected final Socket socket;
|
||||
protected final HTTPSession session;
|
||||
|
||||
public HTTPDModule(HTTPSession session)
|
||||
{
|
||||
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()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getTitle()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getStyle()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getScript()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Response getResponse()
|
||||
{
|
||||
return new HTTPDPageBuilder(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)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Response;
|
||||
|
||||
public class HTTPDPageBuilder
|
||||
{
|
||||
private static final String TEMPLATE = "<!DOCTYPE html>\r\n"
|
||||
+ "<html>\r\n"
|
||||
+ "<head>\r\n"
|
||||
+ "<title>{$TITLE}</title>\r\n"
|
||||
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n"
|
||||
+ "{$STYLE}"
|
||||
+ "{$SCRIPT}"
|
||||
+ "</head>\r\n"
|
||||
+ "<body>\r\n{$BODY}</body>\r\n"
|
||||
+ "</html>\r\n";
|
||||
private static final String STYLE = "<style type=\"text/css\">{$STYLE}</style>\r\n";
|
||||
private static final String SCRIPT = "<script src=\"//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js\"></script>\r\n"
|
||||
+ "<script src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js\"></script>\r\n"
|
||||
+ "<script>\r\n{$SCRIPT}\r\n</script>\r\n";
|
||||
//
|
||||
private String body = null;
|
||||
private String title = null;
|
||||
private String style = null;
|
||||
private String script = null;
|
||||
|
||||
public HTTPDPageBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public HTTPDPageBuilder(String body, String title, String style, String script)
|
||||
{
|
||||
this.body = body;
|
||||
this.title = title;
|
||||
this.style = style;
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public void setBody(String body)
|
||||
{
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public void setTitle(String title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setStyle(String style)
|
||||
{
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
public void setScript(String script)
|
||||
{
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public Response getResponse()
|
||||
{
|
||||
return new Response(this.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return TEMPLATE
|
||||
.replace("{$BODY}", this.body == null ? "" : this.body)
|
||||
.replace("{$TITLE}", this.title == null ? "" : this.title)
|
||||
.replace("{$STYLE}", this.style == null ? "" : STYLE.replace("{$STYLE}", this.style))
|
||||
.replace("{$SCRIPT}", this.script == null ? "" : SCRIPT.replace("{$SCRIPT}", this.script));
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
import me.totalfreedom.totalfreedommod.config.ConfigEntry;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.HTTPSession;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Response;
|
||||
import me.totalfreedom.totalfreedommod.util.FLog;
|
||||
import net.pravian.aero.component.service.AbstractService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class HTTPDaemon extends AbstractService<TotalFreedomMod>
|
||||
{
|
||||
|
||||
public static String MIME_DEFAULT_BINARY = "application/octet-stream";
|
||||
//
|
||||
private static final Pattern EXT_REGEX = Pattern.compile("\\.([^\\.\\s]+)$");
|
||||
//
|
||||
public static final int PORT = ConfigEntry.HTTPD_PORT.getInteger();
|
||||
//
|
||||
private static final TFM_HTTPD HTTPD = new TFM_HTTPD(PORT);
|
||||
|
||||
public HTTPDaemon(TotalFreedomMod plugin)
|
||||
{
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
if (!ConfigEntry.HTTPD_ENABLED.getBoolean())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HTTPD.start();
|
||||
|
||||
if (HTTPD.isAlive())
|
||||
{
|
||||
FLog.info("TFM HTTPd started. Listening on port: " + HTTPD.getListeningPort());
|
||||
}
|
||||
else
|
||||
{
|
||||
FLog.info("Error starting TFM HTTPd.");
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
if (!ConfigEntry.HTTPD_ENABLED.getBoolean())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HTTPD.stop();
|
||||
|
||||
FLog.info("TFM HTTPd stopped.");
|
||||
}
|
||||
|
||||
private static enum ModuleType
|
||||
{
|
||||
|
||||
DUMP(new ModuleExecutable(false, "dump")
|
||||
{
|
||||
@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();
|
||||
}
|
||||
}),
|
||||
PLAYERS(new ModuleExecutable(true, "players")
|
||||
{
|
||||
@Override
|
||||
public Response getResponse(HTTPSession session)
|
||||
{
|
||||
return new Module_players(session).getResponse();
|
||||
}
|
||||
}),
|
||||
LOGS(new ModuleExecutable(false, "logs")
|
||||
{
|
||||
@Override
|
||||
public Response getResponse(HTTPSession session)
|
||||
{
|
||||
return new Module_logs(session).getResponse();
|
||||
}
|
||||
});
|
||||
//
|
||||
private final ModuleExecutable moduleExecutable;
|
||||
|
||||
private ModuleType(ModuleExecutable moduleExecutable)
|
||||
{
|
||||
this.moduleExecutable = moduleExecutable;
|
||||
}
|
||||
|
||||
private abstract static class ModuleExecutable
|
||||
{
|
||||
|
||||
private final boolean runOnBukkitThread;
|
||||
private final String name;
|
||||
|
||||
private 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)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract Response getResponse(HTTPSession session);
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public ModuleExecutable getModuleExecutable()
|
||||
{
|
||||
return moduleExecutable;
|
||||
}
|
||||
|
||||
private static ModuleType getByName(String needle)
|
||||
{
|
||||
for (ModuleType type : values())
|
||||
{
|
||||
if (type.getModuleExecutable().getName().equalsIgnoreCase(needle))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return FILE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TFM_HTTPD extends NanoHTTPD
|
||||
{
|
||||
|
||||
public TFM_HTTPD(int port)
|
||||
{
|
||||
super(port);
|
||||
}
|
||||
|
||||
public TFM_HTTPD(String hostname, int port)
|
||||
{
|
||||
super(hostname, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response serve(HTTPSession session)
|
||||
{
|
||||
Response response;
|
||||
|
||||
try
|
||||
{
|
||||
final String[] args = StringUtils.split(session.getUri(), "/");
|
||||
final ModuleType moduleType = args.length >= 1 ? ModuleType.getByName(args[0]) : ModuleType.FILE;
|
||||
response = moduleType.getModuleExecutable().execute(session);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Error 500: Internal Server Error\r\n" + ex.getMessage() + "\r\n" + ExceptionUtils.getStackTrace(ex));
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
response = new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Error 404: Not Found - The requested resource was not found on this server.");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public static Response serveFileBasic(File file)
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
if (file != null && file.exists())
|
||||
{
|
||||
try
|
||||
{
|
||||
String mimetype = null;
|
||||
|
||||
Matcher matcher = EXT_REGEX.matcher(file.getCanonicalPath());
|
||||
if (matcher.find())
|
||||
{
|
||||
mimetype = Module_file.MIME_TYPES.get(matcher.group(1));
|
||||
}
|
||||
|
||||
if (mimetype == null || mimetype.trim().isEmpty())
|
||||
{
|
||||
mimetype = MIME_DEFAULT_BINARY;
|
||||
}
|
||||
|
||||
response = new Response(Response.Status.OK, mimetype, new FileInputStream(file));
|
||||
response.addHeader("Content-Length", "" + file.length());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import static me.totalfreedom.totalfreedommod.httpd.HTMLGenerationTools.list;
|
||||
import static me.totalfreedom.totalfreedommod.httpd.HTMLGenerationTools.paragraph;
|
||||
import me.totalfreedom.totalfreedommod.util.FLog;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class Module_dump extends HTTPDModule
|
||||
{
|
||||
private File echoFile = null;
|
||||
private final String body;
|
||||
|
||||
public Module_dump(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NanoHTTPD.Response getResponse()
|
||||
{
|
||||
String echo = params.get("echo");
|
||||
boolean doEcho = echo != null && ((echo = echo.toLowerCase().trim()).equalsIgnoreCase("true") || echo.equalsIgnoreCase("1"));
|
||||
|
||||
if (doEcho && this.echoFile != null && this.echoFile.exists())
|
||||
{
|
||||
return HTTPDaemon.serveFileBasic(this.echoFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody()
|
||||
{
|
||||
return body;
|
||||
}
|
||||
|
||||
private String body()
|
||||
{
|
||||
StringBuilder responseBody = new StringBuilder();
|
||||
|
||||
String remoteAddress = socket.getInetAddress().getHostAddress();
|
||||
|
||||
String[] args = StringUtils.split(uri, "/");
|
||||
|
||||
Map<String, String> files = getFiles();
|
||||
|
||||
responseBody
|
||||
.append(paragraph("URI: " + uri))
|
||||
.append(paragraph("args (Length: " + args.length + "): " + StringUtils.join(args, ",")))
|
||||
.append(paragraph("Method: " + method.toString()))
|
||||
.append(paragraph("Remote Address: " + remoteAddress))
|
||||
.append(paragraph("Headers:"))
|
||||
.append(list(headers))
|
||||
.append(paragraph("Params:"))
|
||||
.append(list(params))
|
||||
.append(paragraph("Files:"))
|
||||
.append(list(files));
|
||||
|
||||
Iterator<Map.Entry<String, String>> it = files.entrySet().iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Map.Entry<String, String> entry = it.next();
|
||||
String formName = entry.getKey();
|
||||
String tempFileName = entry.getValue();
|
||||
String origFileName = params.get(formName);
|
||||
|
||||
File tempFile = new File(tempFileName);
|
||||
if (tempFile.exists())
|
||||
{
|
||||
this.echoFile = tempFile;
|
||||
|
||||
if (origFileName.contains("../"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String targetFileName = "./public_html/uploads/" + origFileName;
|
||||
|
||||
File targetFile = new File(targetFileName);
|
||||
|
||||
try
|
||||
{
|
||||
FileUtils.copyFile(tempFile, targetFile);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseBody.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle()
|
||||
{
|
||||
return "TotalFreedomMod :: Request Debug Dumper";
|
||||
}
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import me.totalfreedom.totalfreedommod.config.ConfigEntry;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Response;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/*
|
||||
* This class was adapted from https://github.com/NanoHttpd/nanohttpd/blob/master/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
|
||||
*/
|
||||
public class Module_file extends HTTPDModule
|
||||
{
|
||||
private final File rootDir = new File(ConfigEntry.HTTPD_PUBLIC_FOLDER.getString());
|
||||
public static final Map<String, String> MIME_TYPES = new HashMap<String, String>();
|
||||
|
||||
static
|
||||
{
|
||||
MIME_TYPES.put("css", "text/css");
|
||||
MIME_TYPES.put("htm", "text/html");
|
||||
MIME_TYPES.put("html", "text/html");
|
||||
MIME_TYPES.put("xml", "text/xml");
|
||||
MIME_TYPES.put("java", "text/x-java-source, text/java");
|
||||
MIME_TYPES.put("txt", "text/plain");
|
||||
MIME_TYPES.put("asc", "text/plain");
|
||||
MIME_TYPES.put("yml", "text/yaml");
|
||||
MIME_TYPES.put("gif", "image/gif");
|
||||
MIME_TYPES.put("jpg", "image/jpeg");
|
||||
MIME_TYPES.put("jpeg", "image/jpeg");
|
||||
MIME_TYPES.put("png", "image/png");
|
||||
MIME_TYPES.put("mp3", "audio/mpeg");
|
||||
MIME_TYPES.put("m3u", "audio/mpeg-url");
|
||||
MIME_TYPES.put("mp4", "video/mp4");
|
||||
MIME_TYPES.put("ogv", "video/ogg");
|
||||
MIME_TYPES.put("flv", "video/x-flv");
|
||||
MIME_TYPES.put("mov", "video/quicktime");
|
||||
MIME_TYPES.put("swf", "application/x-shockwave-flash");
|
||||
MIME_TYPES.put("js", "application/javascript");
|
||||
MIME_TYPES.put("pdf", "application/pdf");
|
||||
MIME_TYPES.put("doc", "application/msword");
|
||||
MIME_TYPES.put("ogg", "application/x-ogg");
|
||||
MIME_TYPES.put("zip", "application/octet-stream");
|
||||
MIME_TYPES.put("exe", "application/octet-stream");
|
||||
MIME_TYPES.put("class", "application/octet-stream");
|
||||
}
|
||||
|
||||
public Module_file(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
private File getRootDir()
|
||||
{
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
private String encodeUri(String uri)
|
||||
{
|
||||
String newUri = "";
|
||||
StringTokenizer st = new StringTokenizer(uri, "/ ", true);
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
String tok = st.nextToken();
|
||||
if (tok.equals("/"))
|
||||
{
|
||||
newUri += "/";
|
||||
}
|
||||
else if (tok.equals(" "))
|
||||
{
|
||||
newUri += "%20";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
newUri += URLEncoder.encode(tok, "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
return newUri;
|
||||
}
|
||||
|
||||
public Response serveFile(String uri, Map<String, String> params, File homeDir)
|
||||
{
|
||||
Response res = null;
|
||||
|
||||
// Make sure we won't die of an exception later
|
||||
if (!homeDir.isDirectory())
|
||||
{
|
||||
res = new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERRROR: serveFile(): given homeDir is not a directory.");
|
||||
}
|
||||
|
||||
if (res == null)
|
||||
{
|
||||
// Remove URL arguments
|
||||
uri = uri.trim().replace(File.separatorChar, '/');
|
||||
if (uri.indexOf('?') >= 0)
|
||||
{
|
||||
uri = uri.substring(0, uri.indexOf('?'));
|
||||
}
|
||||
|
||||
// Prohibit getting out of current directory
|
||||
if (uri.startsWith("src/main") || uri.endsWith("src/main") || uri.contains("../"))
|
||||
{
|
||||
res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Won't serve ../ for security reasons.");
|
||||
}
|
||||
}
|
||||
|
||||
File f = new File(homeDir, uri);
|
||||
if (res == null && !f.exists())
|
||||
{
|
||||
res = new Response(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found.");
|
||||
}
|
||||
|
||||
// List the directory, if necessary
|
||||
if (res == null && f.isDirectory())
|
||||
{
|
||||
// Browsers get confused without '/' after the
|
||||
// directory, send a redirect.
|
||||
if (!uri.endsWith("/"))
|
||||
{
|
||||
uri += "/";
|
||||
res = new Response(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" + uri
|
||||
+ "</a></body></html>");
|
||||
res.addHeader("Location", uri);
|
||||
}
|
||||
|
||||
if (res == null)
|
||||
{
|
||||
// First try index.html and index.htm
|
||||
if (new File(f, "index.html").exists())
|
||||
{
|
||||
f = new File(homeDir, uri + "/index.html");
|
||||
}
|
||||
else if (new File(f, "index.htm").exists())
|
||||
{
|
||||
f = new File(homeDir, uri + "/index.htm");
|
||||
}
|
||||
else if (f.canRead())
|
||||
{
|
||||
// No index file, list the directory if it is readable
|
||||
res = new Response(listDirectory(uri, f));
|
||||
}
|
||||
else
|
||||
{
|
||||
res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: No directory listing.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (res == null)
|
||||
{
|
||||
// Get MIME type from file name extension, if possible
|
||||
String mime = null;
|
||||
int dot = f.getCanonicalPath().lastIndexOf('.');
|
||||
if (dot >= 0)
|
||||
{
|
||||
mime = MIME_TYPES.get(f.getCanonicalPath().substring(dot + 1).toLowerCase());
|
||||
}
|
||||
if (mime == null)
|
||||
{
|
||||
mime = HTTPDaemon.MIME_DEFAULT_BINARY;
|
||||
}
|
||||
|
||||
// Calculate etag
|
||||
String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode());
|
||||
|
||||
final long fileLen = f.length();
|
||||
|
||||
long startFrom = 0;
|
||||
long endAt = -1;
|
||||
final String range = params.get("range");
|
||||
if (range != null)
|
||||
{
|
||||
final String[] rangeParams = StringUtils.split(range, "=");
|
||||
if (rangeParams.length >= 2)
|
||||
{
|
||||
if ("bytes".equalsIgnoreCase(rangeParams[0]))
|
||||
{
|
||||
try
|
||||
{
|
||||
int minus = rangeParams[1].indexOf('-');
|
||||
if (minus > 0)
|
||||
{
|
||||
startFrom = Long.parseLong(rangeParams[1].substring(0, minus));
|
||||
endAt = Long.parseLong(rangeParams[1].substring(minus + 1));
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
else if ("tail".equalsIgnoreCase(rangeParams[0]))
|
||||
{
|
||||
try
|
||||
{
|
||||
final long tailLen = Long.parseLong(rangeParams[1]);
|
||||
if (tailLen < fileLen)
|
||||
{
|
||||
startFrom = fileLen - tailLen - 2;
|
||||
if (startFrom < 0)
|
||||
{
|
||||
startFrom = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change return code and add Content-Range header when skipping is requested
|
||||
if (range != null && startFrom >= 0)
|
||||
{
|
||||
if (startFrom >= fileLen)
|
||||
{
|
||||
res = new Response(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
|
||||
res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (endAt < 0)
|
||||
{
|
||||
endAt = fileLen - 1;
|
||||
}
|
||||
long newLen = endAt - startFrom + 1;
|
||||
if (newLen < 0)
|
||||
{
|
||||
newLen = 0;
|
||||
}
|
||||
|
||||
final long dataLen = newLen;
|
||||
FileInputStream fis = new FileInputStream(f)
|
||||
{
|
||||
@Override
|
||||
public int available() throws IOException
|
||||
{
|
||||
return (int) dataLen;
|
||||
}
|
||||
};
|
||||
fis.skip(startFrom);
|
||||
|
||||
res = new Response(Response.Status.PARTIAL_CONTENT, mime, fis);
|
||||
res.addHeader("Content-Length", "" + dataLen);
|
||||
res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = new Response(Response.Status.OK, mime, new FileInputStream(f));
|
||||
res.addHeader("Content-Length", "" + fileLen);
|
||||
res.addHeader("ETag", etag);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
|
||||
}
|
||||
|
||||
res.addHeader("Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes
|
||||
return res;
|
||||
}
|
||||
|
||||
private String listDirectory(String uri, File f)
|
||||
{
|
||||
String heading = "Directory " + uri;
|
||||
String msg = "<html><head><title>" + heading + "</title><style><!--\n"
|
||||
+ "span.dirname { font-weight: bold; }\n"
|
||||
+ "span.filesize { font-size: 75%; }\n"
|
||||
+ "// -->\n"
|
||||
+ "</style>"
|
||||
+ "</head><body><h1>" + heading + "</h1>";
|
||||
|
||||
String up = null;
|
||||
if (uri.length() > 1)
|
||||
{
|
||||
String u = uri.substring(0, uri.length() - 1);
|
||||
int slash = u.lastIndexOf('/');
|
||||
if (slash >= 0 && slash < u.length())
|
||||
{
|
||||
up = uri.substring(0, slash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _files = Arrays.asList(f.list(new FilenameFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File dir, String name)
|
||||
{
|
||||
return new File(dir, name).isFile();
|
||||
}
|
||||
}));
|
||||
Collections.sort(_files);
|
||||
List<String> directories = Arrays.asList(f.list(new FilenameFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File dir, String name)
|
||||
{
|
||||
return new File(dir, name).isDirectory();
|
||||
}
|
||||
}));
|
||||
Collections.sort(directories);
|
||||
if (up != null || directories.size() + _files.size() > 0)
|
||||
{
|
||||
msg += "<ul>";
|
||||
if (up != null || directories.size() > 0)
|
||||
{
|
||||
msg += "<section class=\"directories\">";
|
||||
if (up != null)
|
||||
{
|
||||
msg += "<li><a rel=\"directory\" href=\"" + up + "\"><span class=\"dirname\">..</span></a></b></li>";
|
||||
}
|
||||
for (int i = 0; i < directories.size(); i++)
|
||||
{
|
||||
String dir = directories.get(i) + "/";
|
||||
msg += "<li><a rel=\"directory\" href=\"" + encodeUri(uri + dir) + "\"><span class=\"dirname\">" + dir + "</span></a></b></li>";
|
||||
}
|
||||
msg += "</section>";
|
||||
}
|
||||
if (_files.size() > 0)
|
||||
{
|
||||
msg += "<section class=\"files\">";
|
||||
for (int i = 0; i < _files.size(); i++)
|
||||
{
|
||||
String file = _files.get(i);
|
||||
|
||||
msg += "<li><a href=\"" + encodeUri(uri + file) + "\"><span class=\"filename\">" + file + "</span></a>";
|
||||
File curFile = new File(f, file);
|
||||
long len = curFile.length();
|
||||
msg += " <span class=\"filesize\">(";
|
||||
if (len < 1024)
|
||||
{
|
||||
msg += len + " bytes";
|
||||
}
|
||||
else if (len < 1024 * 1024)
|
||||
{
|
||||
msg += len / 1024 + "." + (len % 1024 / 10 % 100) + " KB";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += len / (1024 * 1024) + "." + len % (1024 * 1024) / 10 % 100 + " MB";
|
||||
}
|
||||
msg += ")</span></li>";
|
||||
}
|
||||
msg += "</section>";
|
||||
}
|
||||
msg += "</ul>";
|
||||
}
|
||||
msg += "</body></html>";
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse()
|
||||
{
|
||||
return serveFile(uri, params, getRootDir());
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import me.totalfreedom.totalfreedommod.commands.FreedomCommand;
|
||||
import static me.totalfreedom.totalfreedommod.httpd.HTMLGenerationTools.heading;
|
||||
import static me.totalfreedom.totalfreedommod.httpd.HTMLGenerationTools.paragraph;
|
||||
import me.totalfreedom.totalfreedommod.rank.PlayerRank;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
import net.pravian.aero.command.CommandReflection;
|
||||
import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.PluginIdentifiableCommand;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
|
||||
public class Module_help extends HTTPDModule
|
||||
{
|
||||
public Module_help(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody()
|
||||
{
|
||||
final CommandMap map = CommandReflection.getCommandMap();
|
||||
if (map == null || !(map instanceof SimpleCommandMap))
|
||||
{
|
||||
return paragraph("Error loading commands.");
|
||||
}
|
||||
|
||||
final StringBuilder responseBody = new StringBuilder()
|
||||
.append(heading("Command Help", 1))
|
||||
.append(paragraph(
|
||||
"This page is an automatically generated listing of all plugin commands that are currently live on the server. "
|
||||
+ "Please note that it does not include vanilla server commands."));
|
||||
|
||||
final Collection<Command> knownCommands = ((SimpleCommandMap) map).getCommands();
|
||||
final Map<String, List<Command>> commandsByPlugin = new HashMap<String, List<Command>>();
|
||||
|
||||
for (Command command : knownCommands)
|
||||
{
|
||||
String pluginName = "Bukkit";
|
||||
if (command instanceof PluginIdentifiableCommand)
|
||||
{
|
||||
pluginName = ((PluginIdentifiableCommand) command).getPlugin().getName();
|
||||
}
|
||||
|
||||
List<Command> pluginCommands = commandsByPlugin.get(pluginName);
|
||||
if (pluginCommands == null)
|
||||
{
|
||||
pluginCommands = Lists.newArrayList();
|
||||
commandsByPlugin.put(pluginName, pluginCommands);
|
||||
}
|
||||
|
||||
pluginCommands.add(command);
|
||||
}
|
||||
|
||||
final Iterator<Map.Entry<String, List<Command>>> it = commandsByPlugin.entrySet().iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
final Map.Entry<String, List<Command>> entry = it.next();
|
||||
final String pluginName = entry.getKey();
|
||||
final List<Command> commands = entry.getValue();
|
||||
|
||||
Collections.sort(commands, new CommandComparator());
|
||||
|
||||
responseBody.append(heading(pluginName, 2)).append("<ul>\r\n");
|
||||
|
||||
PlayerRank lastTfmCommandLevel = null;
|
||||
for (Command command : commands)
|
||||
{
|
||||
if (!TotalFreedomMod.pluginName.equals(pluginName))
|
||||
{
|
||||
responseBody.append(buildDescription(command));
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerRank tfmCommandLevel = FreedomCommand.getCommand(command).getPerms().level();
|
||||
if (lastTfmCommandLevel == null || lastTfmCommandLevel != tfmCommandLevel)
|
||||
{
|
||||
responseBody.append("</ul>\r\n").append(heading(tfmCommandLevel.getName(), 3)).append("<ul>\r\n");
|
||||
}
|
||||
lastTfmCommandLevel = tfmCommandLevel;
|
||||
responseBody.append(buildDescription(command));
|
||||
}
|
||||
|
||||
responseBody.append("</ul>\r\n");
|
||||
}
|
||||
|
||||
return responseBody.toString();
|
||||
}
|
||||
|
||||
private static String buildDescription(Command command)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(
|
||||
"<li><span class=\"commandName\">{$CMD_NAME}</span> - Usage: <span class=\"commandUsage\">{$CMD_USAGE}</span>"
|
||||
.replace("{$CMD_NAME}", escapeHtml4(command.getName().trim()))
|
||||
.replace("{$CMD_USAGE}", escapeHtml4(command.getUsage().trim())));
|
||||
|
||||
if (!command.getAliases().isEmpty())
|
||||
{
|
||||
sb.append(
|
||||
" - Aliases: <span class=\"commandAliases\">{$CMD_ALIASES}</span>"
|
||||
.replace("{$CMD_ALIASES}", escapeHtml4(StringUtils.join(command.getAliases(), ", "))));
|
||||
}
|
||||
|
||||
sb.append(
|
||||
"<br><span class=\"commandDescription\">{$CMD_DESC}</span></li>\r\n"
|
||||
.replace("{$CMD_DESC}", escapeHtml4(command.getDescription().trim())));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle()
|
||||
{
|
||||
return "TotalFreedomMod :: Command Help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStyle()
|
||||
{
|
||||
return ".commandName{font-weight:bold;}.commandDescription{padding-left:15px;}li{margin:.15em;padding:.15em;}";
|
||||
}
|
||||
|
||||
public static class CommandComparator implements Comparator<Command>
|
||||
{
|
||||
@Override
|
||||
public int compare(Command a, Command b)
|
||||
{
|
||||
FreedomCommand ca = FreedomCommand.getCommand(a);
|
||||
FreedomCommand cb = FreedomCommand.getCommand(b);
|
||||
|
||||
if (ca == null
|
||||
|| cb == null
|
||||
|| ca.getPerms() == null
|
||||
|| cb.getPerms() == null)
|
||||
{
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
|
||||
return ca.getPerms().level().getName().compareTo(cb.getPerms().level().getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.util.Collection;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class Module_list extends HTTPDModule
|
||||
{
|
||||
public Module_list(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody()
|
||||
{
|
||||
final StringBuilder body = new StringBuilder();
|
||||
|
||||
final Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||
|
||||
body.append("<p>There are ").append(onlinePlayers.size()).append("/").append(Bukkit.getMaxPlayers()).append(" players online:</p>\r\n");
|
||||
|
||||
body.append("<ul>\r\n");
|
||||
|
||||
for (Player player : onlinePlayers)
|
||||
{
|
||||
String tag = TotalFreedomMod.plugin.rm.getDisplayRank(player).getTag();
|
||||
body.append("<li>").append(tag).append(player.getName()).append("</li>\r\n");
|
||||
}
|
||||
|
||||
body.append("</ul>\r\n");
|
||||
|
||||
return body.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle()
|
||||
{
|
||||
return "Total Freedom - Online Users";
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import me.totalfreedom.totalfreedommod.config.ConfigEntry;
|
||||
|
||||
public class Module_logs extends Module_file
|
||||
{
|
||||
|
||||
public Module_logs(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NanoHTTPD.Response getResponse()
|
||||
{
|
||||
if (ConfigEntry.LOGS_SECRET.getString().equals(params.get("password")))
|
||||
{
|
||||
return serveFile("latest.log", params, new File("./logs"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NanoHTTPD.Response(NanoHTTPD.Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "Incorrect password.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
|
||||
public class Module_permbans extends HTTPDModule
|
||||
{
|
||||
public Module_permbans(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NanoHTTPD.Response getResponse()
|
||||
{
|
||||
File permbanFile = new File(TotalFreedomMod.plugin.getDataFolder(), TotalFreedomMod.PERMBAN_FILENAME);
|
||||
if (permbanFile.exists())
|
||||
{
|
||||
return HTTPDaemon.serveFileBasic(new File(TotalFreedomMod.plugin.getDataFolder(), TotalFreedomMod.PERMBAN_FILENAME));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
|
||||
"Error 404: Not Found - The requested resource was not found on this server.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import me.totalfreedom.totalfreedommod.admin.Admin;
|
||||
import me.totalfreedom.totalfreedommod.util.FUtil;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class Module_players extends HTTPDModule
|
||||
{
|
||||
public Module_players(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public NanoHTTPD.Response getResponse()
|
||||
{
|
||||
final JSONObject responseObject = new JSONObject();
|
||||
|
||||
final JSONArray players = new JSONArray();
|
||||
final JSONArray superadmins = new JSONArray();
|
||||
final JSONArray telnetadmins = new JSONArray();
|
||||
final JSONArray senioradmins = new JSONArray();
|
||||
final JSONArray developers = new JSONArray();
|
||||
|
||||
// All online players
|
||||
for (Player player : Bukkit.getOnlinePlayers())
|
||||
{
|
||||
players.add(player.getName());
|
||||
}
|
||||
|
||||
// Admins
|
||||
for (Admin admin : TotalFreedomMod.plugin.al.getAllAdmins().values())
|
||||
{
|
||||
final String username = admin.getName();
|
||||
|
||||
switch (admin.getRank())
|
||||
{
|
||||
case SUPER_ADMIN:
|
||||
superadmins.add(username);
|
||||
break;
|
||||
case TELNET_ADMIN:
|
||||
telnetadmins.add(username);
|
||||
break;
|
||||
case SENIOR_ADMIN:
|
||||
senioradmins.add(senioradmins);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Developers
|
||||
developers.addAll(FUtil.DEVELOPERS);
|
||||
|
||||
responseObject.put("players", players);
|
||||
responseObject.put("superadmins", superadmins);
|
||||
responseObject.put("telnetadmins", telnetadmins);
|
||||
responseObject.put("senioradmins", senioradmins);
|
||||
responseObject.put("developers", developers);
|
||||
|
||||
final NanoHTTPD.Response response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, NanoHTTPD.MIME_JSON, responseObject.toString());
|
||||
response.addHeader("Access-Control-Allow-Origin", "*");
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,303 @@
|
||||
package me.totalfreedom.totalfreedommod.httpd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import me.totalfreedom.totalfreedommod.admin.Admin;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Method;
|
||||
import me.totalfreedom.totalfreedommod.httpd.NanoHTTPD.Response;
|
||||
import me.totalfreedom.totalfreedommod.util.FLog;
|
||||
import me.totalfreedom.totalfreedommod.TotalFreedomMod;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class Module_schematic extends HTTPDModule
|
||||
{
|
||||
private static final File SCHEMATIC_FOLDER = new File("./plugins/WorldEdit/schematics/");
|
||||
private static final String REQUEST_FORM_FILE_ELEMENT_NAME = "schematicFile";
|
||||
private static final Pattern SCHEMATIC_FILENAME_LC = Pattern.compile("^[a-z0-9_'!,\\-]{1,30}\\.schematic$");
|
||||
private static final String[] SCHEMATIC_FILTER = new String[]
|
||||
{
|
||||
"schematic"
|
||||
};
|
||||
private static final String UPLOAD_FORM = "<form method=\"post\" name=\"schematicForm\" id=\"schematicForm\" action=\"/schematic/upload/\" enctype=\"multipart/form-data\">\n"
|
||||
+ "<p>Select a schematic file to upload. Filenames must be alphanumeric, between 1 and 30 characters long (inclusive), and have a .schematic extension.</p>\n"
|
||||
+ "<input type=\"file\" id=\"schematicFile\" name=\"schematicFile\" />\n"
|
||||
+ "<br />\n"
|
||||
+ "<button type=\"submit\">Submit</button>\n"
|
||||
+ "</form>";
|
||||
|
||||
public Module_schematic(NanoHTTPD.HTTPSession session)
|
||||
{
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new HTTPDPageBuilder(body(), title(), null, null).getResponse();
|
||||
}
|
||||
catch (ResponseOverrideException ex)
|
||||
{
|
||||
return ex.getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
public String title()
|
||||
{
|
||||
return "TotalFreedomMod :: Schematic Manager";
|
||||
}
|
||||
|
||||
public String body() throws ResponseOverrideException
|
||||
{
|
||||
if (!SCHEMATIC_FOLDER.exists())
|
||||
{
|
||||
return HTMLGenerationTools.paragraph("Can't find the WorldEdit schematic folder.");
|
||||
}
|
||||
|
||||
final StringBuilder out = new StringBuilder();
|
||||
|
||||
final String[] args = StringUtils.split(uri, "/");
|
||||
final ModuleMode mode = ModuleMode.getMode(getArg(args, 1));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case LIST:
|
||||
{
|
||||
Collection<File> schematics = FileUtils.listFiles(SCHEMATIC_FOLDER, SCHEMATIC_FILTER, false);
|
||||
|
||||
final List<String> schematicsFormatted = new ArrayList<String>();
|
||||
for (File schematic : schematics)
|
||||
{
|
||||
String filename = StringEscapeUtils.escapeHtml4(schematic.getName());
|
||||
|
||||
if (SCHEMATIC_FILENAME_LC.matcher(filename.trim().toLowerCase()).find())
|
||||
{
|
||||
schematicsFormatted.add("<li><a href=\"/schematic/download?schematicName=" + filename + "\">" + filename + "</a></li>");
|
||||
}
|
||||
else
|
||||
{
|
||||
schematicsFormatted.add("<li>" + filename + " - (Illegal filename, can't download)</li>");
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(schematicsFormatted, new Comparator<String>()
|
||||
{
|
||||
@Override
|
||||
public int compare(String a, String b)
|
||||
{
|
||||
return a.toLowerCase().compareTo(b.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
out
|
||||
.append(HTMLGenerationTools.heading("Schematics:", 1))
|
||||
.append("<ul>")
|
||||
.append(StringUtils.join(schematicsFormatted, "\r\n"))
|
||||
.append("</ul>");
|
||||
|
||||
break;
|
||||
}
|
||||
case DOWNLOAD:
|
||||
{
|
||||
try
|
||||
{
|
||||
throw new ResponseOverrideException(downloadSchematic(params.get("schematicName")));
|
||||
}
|
||||
catch (SchematicTransferException ex)
|
||||
{
|
||||
out.append(HTMLGenerationTools.paragraph("Error downloading schematic: " + ex.getMessage()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPLOAD:
|
||||
{
|
||||
final String remoteAddress = socket.getInetAddress().getHostAddress();
|
||||
if (!isAuthorized(remoteAddress))
|
||||
{
|
||||
out.append(HTMLGenerationTools.paragraph("Schematic upload access denied: Your IP, " + remoteAddress + ", is not registered to a superadmin on this server."));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (method == Method.POST)
|
||||
{
|
||||
try
|
||||
{
|
||||
uploadSchematic();
|
||||
out.append(HTMLGenerationTools.paragraph("Schematic uploaded successfully."));
|
||||
}
|
||||
catch (SchematicTransferException ex)
|
||||
{
|
||||
out.append(HTMLGenerationTools.paragraph("Error uploading schematic: " + ex.getMessage()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(UPLOAD_FORM);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
out.append(HTMLGenerationTools.paragraph("Invalid request mode."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private boolean uploadSchematic() throws SchematicTransferException
|
||||
{
|
||||
Map<String, String> files = getFiles();
|
||||
|
||||
final String tempFileName = files.get(REQUEST_FORM_FILE_ELEMENT_NAME);
|
||||
if (tempFileName == null)
|
||||
{
|
||||
throw new SchematicTransferException("No file transmitted to server.");
|
||||
}
|
||||
|
||||
final File tempFile = new File(tempFileName);
|
||||
if (!tempFile.exists())
|
||||
{
|
||||
throw new SchematicTransferException();
|
||||
}
|
||||
|
||||
String origFileName = params.get(REQUEST_FORM_FILE_ELEMENT_NAME);
|
||||
if (origFileName == null || (origFileName = origFileName.trim()).isEmpty())
|
||||
{
|
||||
throw new SchematicTransferException("Can't resolve original file name.");
|
||||
}
|
||||
|
||||
if (tempFile.length() > FileUtils.ONE_KB * 64L)
|
||||
{
|
||||
throw new SchematicTransferException("Schematic is too big (64kb max).");
|
||||
}
|
||||
|
||||
if (!SCHEMATIC_FILENAME_LC.matcher(origFileName.toLowerCase()).find())
|
||||
{
|
||||
throw new SchematicTransferException("File name must be alphanumeric, between 1 and 30 characters long (inclusive), and have a \".schematic\" extension.");
|
||||
}
|
||||
|
||||
final File targetFile = new File(SCHEMATIC_FOLDER.getPath(), origFileName);
|
||||
if (targetFile.exists())
|
||||
{
|
||||
throw new SchematicTransferException("Schematic already exists on the server.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileUtils.copyFile(tempFile, targetFile);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
FLog.severe(ex);
|
||||
throw new SchematicTransferException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Response downloadSchematic(String schematicName) throws SchematicTransferException
|
||||
{
|
||||
if (schematicName == null || !SCHEMATIC_FILENAME_LC.matcher((schematicName = schematicName.trim()).toLowerCase()).find())
|
||||
{
|
||||
throw new SchematicTransferException("Invalid schematic name requested: " + schematicName);
|
||||
}
|
||||
|
||||
final File targetFile = new File(SCHEMATIC_FOLDER.getPath(), schematicName);
|
||||
if (!targetFile.exists())
|
||||
{
|
||||
throw new SchematicTransferException("Schematic not found: " + schematicName);
|
||||
}
|
||||
|
||||
Response response = HTTPDaemon.serveFileBasic(targetFile);
|
||||
|
||||
response.addHeader("Content-Disposition", "attachment; filename=" + targetFile.getName() + ";");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private boolean isAuthorized(String remoteAddress)
|
||||
{
|
||||
Admin entry = TotalFreedomMod.plugin.al.getEntryByIp(remoteAddress);
|
||||
return entry != null && entry.isActivated();
|
||||
}
|
||||
|
||||
private static class SchematicTransferException extends Exception
|
||||
{
|
||||
public SchematicTransferException()
|
||||
{
|
||||
}
|
||||
|
||||
public SchematicTransferException(String string)
|
||||
{
|
||||
super(string);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResponseOverrideException extends Exception
|
||||
{
|
||||
private final Response response;
|
||||
|
||||
public ResponseOverrideException(Response response)
|
||||
{
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public Response getResponse()
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getArg(String[] args, int index)
|
||||
{
|
||||
String out = (args.length == index + 1 ? args[index] : null);
|
||||
return (out == null ? null : (out.trim().isEmpty() ? null : out.trim()));
|
||||
}
|
||||
|
||||
private static enum ModuleMode
|
||||
{
|
||||
LIST("list"),
|
||||
UPLOAD("upload"),
|
||||
DOWNLOAD("download"),
|
||||
INVALID(null);
|
||||
//
|
||||
private final String modeName;
|
||||
|
||||
private ModuleMode(String modeName)
|
||||
{
|
||||
this.modeName = modeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.modeName;
|
||||
}
|
||||
|
||||
public static ModuleMode getMode(String needle)
|
||||
{
|
||||
for (ModuleMode mode : values())
|
||||
{
|
||||
final String haystack = mode.toString();
|
||||
if (haystack != null && haystack.equalsIgnoreCase(needle))
|
||||
{
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return INVALID;
|
||||
}
|
||||
}
|
||||
}
|
1331
src/main/java/me/totalfreedom/totalfreedommod/httpd/NanoHTTPD.java
Normal file
1331
src/main/java/me/totalfreedom/totalfreedommod/httpd/NanoHTTPD.java
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user