Merge pull request #264 from Fuzzlemann/master

PR for 4.0.0 (Fuzzlemann) (2)
This commit is contained in:
Rsl1122 2017-08-20 10:44:29 +03:00 committed by GitHub
commit e2a587a527
13 changed files with 333 additions and 172 deletions

View File

@ -40,10 +40,12 @@ import main.java.com.djrapitops.plan.database.databases.SQLiteDB;
import main.java.com.djrapitops.plan.locale.Locale;
import main.java.com.djrapitops.plan.locale.Msg;
import main.java.com.djrapitops.plan.ui.webserver.WebServer;
import main.java.com.djrapitops.plan.ui.webserver.api.bukkit.*;
import main.java.com.djrapitops.plan.utilities.Benchmark;
import main.java.com.djrapitops.plan.utilities.Check;
import main.java.com.djrapitops.plan.utilities.MiscUtils;
import main.java.com.djrapitops.plan.utilities.metrics.BStats;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPIManager;
import org.apache.logging.log4j.LogManager;
import org.bukkit.ChatColor;
@ -174,6 +176,7 @@ public class Plan extends BukkitPlugin<Plan> {
if (analysisRefreshTaskIsEnabled) {
startAnalysisRefreshTask(analysisRefreshMinutes);
}
Benchmark.stop("Enable", "Analysis refresh task registration");
Benchmark.start("WebServer Initialization");
@ -185,6 +188,7 @@ public class Plan extends BukkitPlugin<Plan> {
uiServer = new WebServer(this);
if (webserverIsEnabled) {
registerWebAPIs();
uiServer.initServer();
if (!uiServer.isEnabled()) {
@ -198,6 +202,7 @@ public class Plan extends BukkitPlugin<Plan> {
if (!usingAlternativeIP && serverVariableHolder.getIp().isEmpty()) {
Log.infoColor(Locale.get(Msg.ENABLE_NOTIFY_EMPTY_IP).toString());
}
Benchmark.stop("Enable", "WebServer Initialization");
registerCommand(new PlanCommand(this));
@ -293,6 +298,14 @@ public class Plan extends BukkitPlugin<Plan> {
Benchmark.stop("Enable", "Register Listeners");
}
private void registerWebAPIs() {
WebAPIManager.registerNewAPI("analytics", new AnalyticsWebAPI());
WebAPIManager.registerNewAPI("analyze", new AnalyzeWebAPI());
WebAPIManager.registerNewAPI("configure", new ConfigureWebAPI());
WebAPIManager.registerNewAPI("inspection", new InspectionWebAPI());
WebAPIManager.registerNewAPI("inspect", new InspectWebAPI());
}
/**
* Initializes the database according to settings in the config.
* <p>
@ -300,7 +313,7 @@ public class Plan extends BukkitPlugin<Plan> {
*
* @return true if init was successful, false if not.
*/
public boolean initDatabase() {
private boolean initDatabase() {
databases = new HashSet<>();
databases.add(new MySQLDB(this));
databases.add(new SQLiteDB(this));

View File

@ -72,24 +72,7 @@ public abstract class RawData {
* @param value Any value the placeholder should be replaced with.
*/
public void addValue(String placeholder, Serializable value) {
replaceMap.put(addPlaceholderSigns(placeholder), value.toString());
}
private String addPlaceholderSigns(String placeholder) {
StringBuilder newPlaceholder = new StringBuilder();
if (placeholder.charAt(0) != '%') {
newPlaceholder.append("%");
}
newPlaceholder.append(placeholder);
int lastIndex = placeholder.length() - 1;
if (placeholder.charAt(lastIndex) != '%') {
newPlaceholder.append("%");
}
return newPlaceholder.toString();
replaceMap.put(placeholder, value.toString());
}
/**
@ -102,12 +85,12 @@ public abstract class RawData {
}
/**
* Used to get the value for a placeholder with or without the % symbols.
* Used to get the value for a placeholder without the placeholder prefix and suffix.
*
* @param key placeholder with or without % symbols.
* @param key placeholder without the prefix and suffix
* @return Value the placeholder should be replaced with or null.
*/
public String get(String key) {
return replaceMap.get(addPlaceholderSigns(key));
return replaceMap.get(key);
}
}

View File

@ -5,7 +5,6 @@ import com.sun.net.httpserver.*;
import main.java.com.djrapitops.plan.Log;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.Settings;
import main.java.com.djrapitops.plan.data.UserData;
import main.java.com.djrapitops.plan.data.WebUser;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.database.tables.SecurityTable;
@ -15,13 +14,13 @@ import main.java.com.djrapitops.plan.ui.html.DataRequestHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.*;
import main.java.com.djrapitops.plan.ui.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.ui.webserver.response.api.JsonResponse;
import main.java.com.djrapitops.plan.ui.webserver.response.api.SuccessResponse;
import main.java.com.djrapitops.plan.utilities.Benchmark;
import main.java.com.djrapitops.plan.utilities.HtmlUtils;
import main.java.com.djrapitops.plan.utilities.PassEncryptUtil;
import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPIManager;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.FileConfiguration;
import javax.net.ssl.*;
import java.io.*;
@ -35,6 +34,7 @@ import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
@ -268,13 +268,22 @@ public class WebServer {
}
private String readPOSTRequest(HttpExchange exchange) throws IOException {
byte[] bytes;
try (InputStream in = exchange.getRequestBody()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte buf[] = new byte[4096];
byte[] buf = new byte[4096];
for (int n = in.read(buf); n > 0; n = in.read(buf)) {
out.write(buf, 0, n);
}
return new String(out.toByteArray(), "ISO-8859-1");
bytes = out.toByteArray();
}
try {
return new String(bytes, "ISO-8859-1");
} catch (Exception e) {
return null;
}
}
@ -283,119 +292,46 @@ public class WebServer {
if (args.length < 3) {
String error = "API Method not specified";
return PageCacheHandler.loadPage(error, () -> new NotFoundResponse(error));
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
String method = args[2];
String response = readPOSTRequest(exchange);
if (response == null) {
String error = "Error at reading the POST request." +
"Note that the Encoding must be ISO-8859-1.";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
Map<String, String> variables = readVariables(response);
//TODO ADD CHECK IF SERVER KEY VALID
Plan plan = Plan.getInstance();
String playerString;
UUID uuid;
String identifier;
WebAPI api = WebAPIManager.getAPI(method);
switch (method) {
//TODO Add Bungee APIs
case "analyze":
plan.getAnalysisCache().updateCache();
return PageCacheHandler.loadPage("success", SuccessResponse::new);
case "inspect":
playerString = variables.get("player");
if (api == null) {
String error = "API Method not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
if (playerString == null) {
String error = "Player String not included";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
uuid = UUIDUtility.getUUIDOf(playerString);
if (uuid == null) {
String error = "UUID not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
Plan.getInstance().getInspectCache().cache(uuid);
return PageCacheHandler.loadPage("success", SuccessResponse::new);
case "analysis":
case "analytics":
identifier = "analysisJson";
if (!PageCacheHandler.isCached(identifier)) {
return PageCacheHandler.loadPage("No Analysis Data", () -> new BadRequestResponse("No analysis data available"));
}
return PageCacheHandler.loadPage(identifier);
case "inspection":
playerString = variables.get("player");
if (playerString == null) {
String error = "Player String not included";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
uuid = UUIDUtility.getUUIDOf(playerString);
if (uuid == null) {
String error = "UUID not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
UserData userData = plan.getInspectCache().getFromCache(uuid);
if (userData == null) {
String error = "User not cached";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
return PageCacheHandler.loadPage("inspectionJson: " + uuid, () -> new JsonResponse(plan.getInspectCache().getFromCache(uuid)));
case "configure":
String key = variables.get("configKey");
if (key == null) {
String error = "Config Key null";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
String value = variables.get("configValue");
if (value == null) {
String error = "Config Value null";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
if (value.equals("null")) {
value = null;
}
FileConfiguration config = plan.getConfig();
config.set(key, value);
plan.saveConfig();
return PageCacheHandler.loadPage("success", SuccessResponse::new);
default:
String error = "API Method not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
try {
return api.onResponse(plan, variables);
} catch (Exception ex) {
Log.toLog("WebServer.getAPIResponse", ex);
return new InternalErrorResponse(ex, "An error while processing the request happened");
}
}
private Map<String, String> readVariables(String response) {
Map<String, String> variableMap = new HashMap<>();
String[] variables = response.split("&");
for (String variable : variables) {
String[] splittedVariables = variable.split("=", 2);
if (splittedVariables.length != 2) {
continue;
}
variableMap.put(splittedVariables[0], splittedVariables[1]);
}
return variableMap;
return Arrays.stream(variables)
.map(variable -> variable.split("=", 2))
.filter(splittedVariables -> splittedVariables.length == 2)
.collect(Collectors.toMap(splittedVariables -> splittedVariables[0], splittedVariables -> splittedVariables[1], (a, b) -> b));
}
private Response getResponse(String target, WebUser user) {

View File

@ -0,0 +1,29 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.ui.webserver.api.bukkit;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import main.java.com.djrapitops.plan.ui.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import java.util.Map;
/**
* @author Fuzzlemann
*/
public class AnalyticsWebAPI implements WebAPI {
@Override
public Response onResponse(Plan plan, Map<String, String> variables) {
String identifier = "analysisJson";
if (!PageCacheHandler.isCached(identifier)) {
return PageCacheHandler.loadPage("No Analysis Data", () -> new BadRequestResponse("No analysis data available"));
}
return PageCacheHandler.loadPage(identifier);
}
}

View File

@ -0,0 +1,24 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.ui.webserver.api.bukkit;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import main.java.com.djrapitops.plan.ui.webserver.response.api.SuccessResponse;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import java.util.Map;
/**
* @author Fuzzlemann
*/
public class AnalyzeWebAPI implements WebAPI {
@Override
public Response onResponse(Plan plan, Map<String, String> variables) {
plan.getAnalysisCache().updateCache();
return PageCacheHandler.loadPage("success", SuccessResponse::new);
}
}

View File

@ -0,0 +1,48 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.ui.webserver.api.bukkit;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import main.java.com.djrapitops.plan.ui.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.ui.webserver.response.api.SuccessResponse;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.Map;
/**
* @author Fuzzlemann
*/
public class ConfigureWebAPI implements WebAPI {
@Override
public Response onResponse(Plan plan, Map<String, String> variables) {
String key = variables.get("configKey");
if (key == null) {
String error = "Config Key null";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
String value = variables.get("configValue");
if (value == null) {
String error = "Config Value null";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
if (value.equals("null")) {
value = null;
}
FileConfiguration config = plan.getConfig();
config.set(key, value);
plan.saveConfig();
return PageCacheHandler.loadPage("success", SuccessResponse::new);
}
}

View File

@ -0,0 +1,42 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.ui.webserver.api.bukkit;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import main.java.com.djrapitops.plan.ui.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.ui.webserver.response.api.SuccessResponse;
import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import java.util.Map;
import java.util.UUID;
/**
* @author Fuzzlemann
*/
public class InspectWebAPI implements WebAPI {
@Override
public Response onResponse(Plan plan, Map<String, String> variables) {
String playerString = variables.get("player");
if (playerString == null) {
String error = "Player String not included";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
UUID uuid = UUIDUtility.getUUIDOf(playerString);
if (uuid == null) {
String error = "UUID not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
Plan.getInstance().getInspectCache().cache(uuid);
return PageCacheHandler.loadPage("success", SuccessResponse::new);
}
}

View File

@ -0,0 +1,48 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.ui.webserver.api.bukkit;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.UserData;
import main.java.com.djrapitops.plan.data.cache.PageCacheHandler;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import main.java.com.djrapitops.plan.ui.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.ui.webserver.response.api.JsonResponse;
import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility;
import main.java.com.djrapitops.plan.utilities.webserver.api.WebAPI;
import java.util.Map;
import java.util.UUID;
/**
* @author Fuzzlemann
*/
public class InspectionWebAPI implements WebAPI {
@Override
public Response onResponse(Plan plan, Map<String, String> variables) {
String playerString = variables.get("player");
if (playerString == null) {
String error = "Player String not included";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
UUID uuid = UUIDUtility.getUUIDOf(playerString);
if (uuid == null) {
String error = "UUID not found";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
UserData userData = plan.getInspectCache().getFromCache(uuid);
if (userData == null) {
String error = "User not cached";
return PageCacheHandler.loadPage(error, () -> new BadRequestResponse(error));
}
return PageCacheHandler.loadPage("inspectionJson: " + uuid, () -> new JsonResponse(plan.getInspectCache().getFromCache(uuid)));
}
}

View File

@ -9,18 +9,23 @@ import main.java.com.djrapitops.plan.ui.html.Html;
public class InternalErrorResponse extends Response {
public InternalErrorResponse(Throwable e, String cause) {
super.setHeader("HTTP/1.1 500 Internal Error");
StringBuilder content = new StringBuilder();
super.setHeader("HTTP/1.1 500 Internal Error");
content.append("<h1>500 Internal Error occurred</h1>");
content.append("<p>Please report this issue here: </p>");
content.append(Html.LINK.parse("https://github.com/Rsl1122/Plan-PlayerAnalytics/issues", "Issues"));
content.append("<p>");
content.append(e).append(" | ").append(cause);
for (Object element : e.getStackTrace()) {
for (StackTraceElement element : e.getStackTrace()) {
content.append("<br>");
content.append(" ").append(element);
}
content.append("</p>");
super.setContent(content.toString());
}
}

View File

@ -7,6 +7,7 @@ import main.java.com.djrapitops.plan.locale.Msg;
import main.java.com.djrapitops.plan.ui.html.Html;
import main.java.com.djrapitops.plan.ui.webserver.WebServer;
import main.java.com.djrapitops.plan.utilities.file.FileUtil;
import org.apache.commons.lang.text.StrSubstitutor;
import java.io.FileNotFoundException;
import java.io.Serializable;
@ -40,14 +41,9 @@ public class HtmlUtils {
* @return
*/
public static String replacePlaceholders(String html, Map<String, Serializable> replaceMap) {
for (Map.Entry<String, Serializable> entrySet : replaceMap.entrySet()) {
String placeholder = entrySet.getKey();
String replacer = entrySet.getValue().toString();
StrSubstitutor sub = new StrSubstitutor(replaceMap);
html = html.replace(placeholder, replacer);
}
return html;
return sub.replace(html);
}
/**

View File

@ -0,0 +1,18 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.utilities.webserver.api;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.ui.webserver.response.Response;
import java.util.Map;
/**
* @author Fuzzlemann
*/
public interface WebAPI {
Response onResponse(Plan plan, Map<String, String> variables);
}

View File

@ -0,0 +1,31 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package main.java.com.djrapitops.plan.utilities.webserver.api;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fuzzlemann
*/
public class WebAPIManager {
/**
* Constructor used to hide the public constructor
*/
private WebAPIManager() {
throw new IllegalStateException("Utility class");
}
private static Map<String, WebAPI> registry = new HashMap<>();
public static void registerNewAPI(String method, WebAPI api) {
registry.put(method.toLowerCase(), api);
}
public static WebAPI getAPI(String method) {
return registry.get(method.toLowerCase());
}
}

View File

@ -5,21 +5,21 @@
*/
package test.java.main.java.com.djrapitops.plan.utilities;
import com.google.common.collect.ImmutableMap;
import main.java.com.djrapitops.plan.utilities.HtmlUtils;
import org.bukkit.plugin.java.JavaPlugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import test.java.utils.RandomData;
import test.java.utils.TestInit;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Rsl1122
@ -28,62 +28,50 @@ import static org.junit.Assert.assertTrue;
@PrepareForTest(JavaPlugin.class)
public class HtmlUtilsTest {
/**
*
*/
public HtmlUtilsTest() {
}
/**
*
*/
@Before
public void setUp() throws Exception {
}
/**
* @throws Exception
*/
@Test
public void testGetHtmlStringFromResource() throws Exception {
TestInit.init();
String fileName = "player.html";
String result = HtmlUtils.getStringFromResource(fileName);
assertTrue("Result empty", !result.isEmpty());
assertFalse("Result empty", result.isEmpty());
}
/**
*
*/
@Test
public void testReplacePlaceholders() {
String html = "%test%";
Map<String, Serializable> replaceMap = new HashMap<>();
replaceMap.put("%test%", "Success");
String expResult = "Success";
String randomString = RandomData.randomString(100);
String randomIdentifier = RandomData.randomString(5);
String html = "${" + randomIdentifier + "}" + randomString;
Map<String, Serializable> replaceMap = ImmutableMap.of(randomIdentifier, "Success");
String expResult = "Success" + randomString;
String result = HtmlUtils.replacePlaceholders(html, replaceMap);
assertEquals(expResult, result);
}
/**
*
*/
@Test
public void testReplacePlaceholdersBackslash() {
Map<String, Serializable> replace = new HashMap<>();
replace.put("%test%", "/\\");
String result = HtmlUtils.replacePlaceholders("%test% alright %test%", replace);
String exp = "/\\ alright /\\";
assertEquals(result, exp);
String randomIdentifier = RandomData.randomString(5);
Map<String, Serializable> replace = ImmutableMap.of(randomIdentifier, "/\\");
String expResult = "/\\ alright /\\";
String result = HtmlUtils.replacePlaceholders("${" + randomIdentifier + "} alright ${" + randomIdentifier + "}", replace);
assertEquals(result, expResult);
}
/**
*
*/
@Test
public void testRemoveXSS() {
String xss = "<script></script><!--";
boolean passed = HtmlUtils.removeXSS(xss).length() < xss.length();
assertEquals(true, passed);
String randomString = RandomData.randomString(10);
String xss = "<script>" + randomString + "</script><!--";
String result = HtmlUtils.removeXSS(xss);
assertEquals(randomString, result);
}
}