Restructured WebServer Response serving

This commit is contained in:
Rsl1122 2017-09-13 18:52:53 +03:00
parent 5e9624cba6
commit 3ffc5dd95d
14 changed files with 581 additions and 388 deletions

View File

@ -0,0 +1,27 @@
/*
* 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.api.exceptions;
/**
* Thrown when WebUser can not be authorized (WebServer).
*
* @author Rsl1122
*/
public class WebUserAuthException extends Exception {
public WebUserAuthException() {
}
public WebUserAuthException(String message) {
super(message);
}
public WebUserAuthException(String message, Throwable cause) {
super(message, cause);
}
public WebUserAuthException(Throwable cause) {
super(cause);
}
}

View File

@ -185,7 +185,10 @@ public class UserInfoTable extends UserIDTable {
boolean banned = set.getBoolean(columnBanned);
String name = set.getString("name");
UUID uuid = UUID.fromString(set.getString("uuid"));
userInfo.add(new UserInfo(uuid, name, registered, opped, banned));
UserInfo info = new UserInfo(uuid, name, registered, opped, banned);
if (!userInfo.contains(info)) {
userInfo.add(info);
}
}
return userInfo;
} finally {

View File

@ -6,7 +6,7 @@ import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.record.Country;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.utilities.MiscUtils;
import java.io.File;
import java.io.FileOutputStream;
@ -29,7 +29,7 @@ import java.util.zip.GZIPInputStream;
*/
public class GeolocationCache {
private static File geolocationDB = new File(Plan.getInstance().getDataFolder(), "GeoIP.dat");
private static File geolocationDB = new File(MiscUtils.getIPlan().getDataFolder(), "GeoIP.dat");
private static final Cache<String, String> geolocationCache = CacheBuilder.newBuilder()
.build();
@ -51,7 +51,7 @@ public class GeolocationCache {
* <p>
* An exception from that rule is when the country is unknown or the retrieval of the country failed in any way,
* if that happens, "Not Known" will be returned.
* @see #getUncachedCountry(String)
* @see #getUnCachedCountry(String)
*/
public static String getCountry(String ipAddress) {
String country = getCachedCountry(ipAddress);
@ -59,7 +59,7 @@ public class GeolocationCache {
if (country != null) {
return country;
} else {
country = getUncachedCountry(ipAddress);
country = getUnCachedCountry(ipAddress);
geolocationCache.put(ipAddress, country);
return country;
@ -71,6 +71,7 @@ public class GeolocationCache {
* <p>
* This product includes GeoLite2 data created by MaxMind, available from
* <a href="http://www.maxmind.com">http://www.maxmind.com</a>.
*
* @param ipAddress The IP Address from which the country is retrieved
* @return The name of the country in full length.
* <p>
@ -79,7 +80,7 @@ public class GeolocationCache {
* @see <a href="http://maxmind.com">http://maxmind.com</a>
* @see #getCountry(String)
*/
private static String getUncachedCountry(String ipAddress) {
private static String getUnCachedCountry(String ipAddress) {
try {
checkDB();

View File

@ -12,20 +12,20 @@ import java.util.Map;
*/
public class WebAPIManager {
private static final Map<String, WebAPI> registry = new HashMap<>();
private final Map<String, WebAPI> registry;
/**
* Constructor used to hide the public constructor
*/
private WebAPIManager() {
throw new IllegalStateException("Utility class");
public WebAPIManager() {
registry = new HashMap<>();
}
public static void registerNewAPI(String method, WebAPI api) {
public void registerNewAPI(String method, WebAPI api) {
registry.put(method.toLowerCase(), api);
}
public static WebAPI getAPI(String method) {
public WebAPI getAPI(String method) {
return registry.get(method.toLowerCase());
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.systems.webserver;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.systems.webapi.WebAPI;
import main.java.com.djrapitops.plan.systems.webapi.WebAPIManager;
import main.java.com.djrapitops.plan.systems.webserver.response.ForbiddenResponse;
import main.java.com.djrapitops.plan.systems.webserver.response.Response;
import main.java.com.djrapitops.plan.systems.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.utilities.MiscUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* //TODO Class Javadoc Comment
*
* @author Rsl1122
*/
public class APIResponseHandler {
private final WebAPIManager webAPI;
public APIResponseHandler(WebAPIManager webAPI) {
this.webAPI = webAPI;
}
Response getAPIResponse(Request request) throws IOException {
String target = request.getTarget();
String[] args = target.split("/");
if (args.length < 3) {
String error = "API Method not specified";
return PageCache.loadPage(error, () -> new BadRequestResponse(error));
}
String method = args[2];
String response = null;
try (InputStream inputStream = request.getRequestBody()) {
response = readPOSTRequest(inputStream);
}
if (response == null) {
String error = "Error at reading the POST request." +
"Note that the Encoding must be ISO-8859-1.";
return PageCache.loadPage(error, () -> new BadRequestResponse(error));
}
Map<String, String> variables = readVariables(response);
String key = variables.get("key");
if (!checkKey(key)) {
String error = "Server Key not given or invalid";
return PageCache.loadPage(error, () -> {
ForbiddenResponse forbidden = new ForbiddenResponse();
forbidden.setContent(error);
return forbidden;
});
}
WebAPI api = webAPI.getAPI(method);
if (api == null) {
String error = "API Method not found";
return PageCache.loadPage(error, () -> new BadRequestResponse(error));
}
return api.onResponse(Plan.getInstance(), variables);
}
private String readPOSTRequest(InputStream in) throws IOException {
byte[] bytes;
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
for (int n = in.read(buf); n > 0; n = in.read(buf)) {
out.write(buf, 0, n);
}
bytes = out.toByteArray();
try {
return new String(bytes, StandardCharsets.ISO_8859_1);
} catch (Exception e) {
return null;
}
}
private boolean checkKey(String key) {
if (key == null) {
return false;
}
UUID uuid = MiscUtils.getIPlan().getServerInfoManager().getServerUUID();
UUID keyUUID;
try {
keyUUID = UUID.fromString(key);
} catch (IllegalArgumentException e) {
return false;
}
return uuid.equals(keyUUID);
}
private Map<String, String> readVariables(String response) {
String[] variables = response.split("&");
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));
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.systems.webserver;
import com.djrapitops.plugin.utilities.Verify;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import java.io.InputStream;
import java.util.List;
/**
* //TODO Class Javadoc Comment
*
* @author Rsl1122
*/
public class Request {
private String auth;
private final String requestMethod;
private final String target;
private final HttpExchange exchange;
public Request(HttpExchange exchange) {
this.requestMethod = exchange.getRequestMethod();
this.target = exchange.getRequestURI().toString();
this.exchange = exchange;
setAuth(exchange.getRequestHeaders());
}
public String getAuth() {
return auth;
}
public void setAuth(Headers requestHeaders) {
List<String> authorization = requestHeaders.get("Authorization");
if (Verify.isEmpty(authorization)) {
return;
}
String authLine = authorization.get(0);
if (authLine.contains("Basic ")) {
auth = authLine.split(" ")[1];
}
}
public boolean hasAuth() {
return auth != null;
}
public String getRequestMethod() {
return requestMethod;
}
public String getTarget() {
return target;
}
public boolean isAPIRequest() {
return "POST".equals(requestMethod);
}
public InputStream getRequestBody() {
return exchange.getRequestBody();
}
}

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.systems.webserver;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import main.java.com.djrapitops.plan.api.IPlan;
import main.java.com.djrapitops.plan.systems.webserver.response.PromptAuthorizationResponse;
import main.java.com.djrapitops.plan.systems.webserver.response.Response;
import java.io.IOException;
/**
* HttpHandler for webserver request management.
*
* @author Rsl1122
*/
public class RequestHandler implements HttpHandler {
private final ResponseHandler responseHandler;
RequestHandler(IPlan plugin, WebServer webServer) {
responseHandler = new ResponseHandler(plugin, webServer);
}
@Override
public void handle(HttpExchange exchange) throws IOException {
Headers responseHeaders = exchange.getResponseHeaders();
Request request = new Request(exchange);
try {
Response response = responseHandler.getResponse(request);
if (response instanceof PromptAuthorizationResponse) {
responseHeaders.set("WWW-Authenticate", "Basic realm=\"/\";");
}
response.setResponseHeaders(responseHeaders);
response.send(exchange);
} finally {
exchange.close();
}
}
}

View File

@ -0,0 +1,210 @@
/*
* 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.systems.webserver;
import main.java.com.djrapitops.plan.api.IPlan;
import main.java.com.djrapitops.plan.api.exceptions.WebUserAuthException;
import main.java.com.djrapitops.plan.data.WebUser;
import main.java.com.djrapitops.plan.database.tables.SecurityTable;
import main.java.com.djrapitops.plan.systems.webserver.response.*;
import main.java.com.djrapitops.plan.utilities.PassEncryptUtil;
import main.java.com.djrapitops.plan.utilities.html.HtmlUtils;
import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility;
import java.sql.SQLException;
import java.util.Base64;
import java.util.UUID;
/**
* //TODO Class Javadoc Comment
*
* @author Rsl1122
*/
public class ResponseHandler extends APIResponseHandler {
private final IPlan plugin;
private final WebServer webServer;
private final boolean usingHttps;
public ResponseHandler(IPlan plugin, WebServer webServer) {
super(webServer.getWebAPI());
this.plugin = plugin;
this.webServer = webServer;
this.usingHttps = webServer.isUsingHTTPS();
}
public Response getResponse(Request request) {
String target = request.getTarget();
String[] args = target.split("/");
try {
if ("/favicon.ico".equals(target)) {
return PageCache.loadPage("Redirect: favicon", () -> new RedirectResponse("https://puu.sh/tK0KL/6aa2ba141b.ico"));
}
if (request.isAPIRequest()) {
return getAPIResponse(request);
}
if (target.endsWith(".css")) {
return PageCache.loadPage(target + "css", () -> new CSSResponse("main.css"));
}
if (target.endsWith(".js")) {
String fileName = args[args.length - 1];
return PageCache.loadPage(target + "js", () -> new JavaScriptResponse(fileName));
}
if (usingHttps) {
if (!request.hasAuth()) {
throw new WebUserAuthException("No Authorization");
}
WebUser user = getUser(request.getAuth());
int required = getRequiredPermLevel(target, user.getName());
int permLevel = user.getPermLevel();
if (!isAuthorized(required, permLevel)) {
return forbiddenResponse(required, permLevel);
}
if (args.length < 2) {
return rootPageResponse(user);
}
}
String page = args[1];
switch (page) {
case "players":
return PageCache.loadPage("players", PlayersPageResponse::new);
case "player":
return playerResponse(args);
case "server":
return serverResponse();
default:
return notFoundResponse();
}
} catch (WebUserAuthException e) {
return PageCache.loadPage("promptAuthorization", PromptAuthorizationResponse::new);
} catch (Exception e) {
return new InternalErrorResponse(e, request.getTarget());
}
}
private Response forbiddenResponse(int required, int permLevel) {
return PageCache.loadPage("forbidden", () ->
new ForbiddenResponse("Unauthorized User.<br>"
+ "Make sure your user has the correct access level.<br>"
+ "This page requires permission level of " + required + ",<br>"
+ "This user has permission level of " + permLevel));
}
private boolean isAuthorized(int requiredPermLevel, int permLevel) throws Exception {
return permLevel <= requiredPermLevel;
}
private WebUser getUser(String auth) throws SQLException, PassEncryptUtil.InvalidHashException, PassEncryptUtil.CannotPerformOperationException, WebUserAuthException {
Base64.Decoder decoder = Base64.getDecoder();
byte[] decoded = decoder.decode(auth);
String[] userInfo = new String(decoded).split(":");
if (userInfo.length != 2) {
throw new WebUserAuthException("User and Password not specified");
}
String user = userInfo[0];
String passwordRaw = userInfo[1];
SecurityTable securityTable = plugin.getDB().getSecurityTable();
if (!securityTable.userExists(user)) {
throw new WebUserAuthException("User Doesn't exist");
}
WebUser webUser = securityTable.getWebUser(user);
boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, webUser.getSaltedPassHash());
if (!correctPass) {
throw new WebUserAuthException("User and Password do not match");
}
return webUser;
}
private int getRequiredPermLevel(String target, String user) {
String[] t = target.split("/");
if (t.length < 2) {
return 100;
}
if (t.length > 3) {
return 0;
}
String page = t[1];
switch (page) {
case "players":
return 1;
case "player":
// /player/ - 404 for perm lvl 1
if (t.length < 3) {
return 1;
}
final String wantedUser = t[2].toLowerCase().trim();
final String theUser = user.trim().toLowerCase();
return wantedUser.equals(theUser) ? 2 : 1;
default:
return 0;
}
}
private Response rootPageResponse(WebUser user) {
if (user == null) {
return notFoundResponse();
}
switch (user.getPermLevel()) {
case 0:
return serverResponse();
case 1:
return PageCache.loadPage("players", PlayersPageResponse::new);
case 2:
return playerResponse(new String[]{"", "", user.getName()});
default:
return forbiddenResponse(user.getPermLevel(), 0);
}
}
private Response serverResponse() {
if (!plugin.getInfoManager().isAnalysisCached()) {
String error = "Analysis Data was not cached.<br>Use /plan analyze to cache the Data.";
PageCache.loadPage("notFound: " + error, () -> new NotFoundResponse(error));
}
return PageCache.loadPage("analysisPage", () -> new AnalysisPageResponse(plugin.getInfoManager()));
}
private Response playerResponse(String[] args) {
if (args.length < 3) {
return PageCache.loadPage("notFound", NotFoundResponse::new);
}
String playerName = args[2].trim();
UUID uuid = UUIDUtility.getUUIDOf(playerName);
if (uuid == null) {
String error = "Player has no UUID";
return PageCache.loadPage("notFound: " + error, () -> new NotFoundResponse(error));
}
plugin.getInfoManager().cachePlayer(uuid);
return PageCache.loadPage("inspectPage: " + uuid, () -> new InspectPageResponse(plugin.getInfoManager(), uuid));
}
private Response notFoundResponse() {
String error = "404 Not Found";
return PageCache.loadPage("notFound: " + error, () ->
new NotFoundResponse("Make sure you're accessing a link given by a command, Examples:</p>"
+ "<p>" + webServer.getProtocol() + ":" + HtmlUtils.getInspectUrl("<player>") + " or<br>"
+ webServer.getProtocol() + ":" + HtmlUtils.getServerAnalysisUrl())
);
}
}

View File

@ -1,43 +1,33 @@
package main.java.com.djrapitops.plan.systems.webserver;
import com.djrapitops.plugin.utilities.Verify;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
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.api.IPlan;
import main.java.com.djrapitops.plan.data.WebUser;
import main.java.com.djrapitops.plan.database.tables.SecurityTable;
import main.java.com.djrapitops.plan.locale.Locale;
import main.java.com.djrapitops.plan.locale.Msg;
import main.java.com.djrapitops.plan.systems.info.InformationManager;
import main.java.com.djrapitops.plan.systems.webapi.WebAPI;
import main.java.com.djrapitops.plan.systems.webapi.WebAPIManager;
import main.java.com.djrapitops.plan.systems.webapi.bukkit.*;
import main.java.com.djrapitops.plan.systems.webapi.universal.PingWebAPI;
import main.java.com.djrapitops.plan.systems.webserver.response.*;
import main.java.com.djrapitops.plan.systems.webserver.response.api.BadRequestResponse;
import main.java.com.djrapitops.plan.systems.webserver.response.api.JsonResponse;
import main.java.com.djrapitops.plan.utilities.Benchmark;
import main.java.com.djrapitops.plan.utilities.PassEncryptUtil;
import main.java.com.djrapitops.plan.utilities.html.HtmlUtils;
import main.java.com.djrapitops.plan.utilities.uuid.UUIDUtility;
import javax.net.ssl.*;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
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;
/**
* @author Rsl1122
@ -46,6 +36,7 @@ public class WebServer {
private final IPlan plugin;
private InformationManager infoManager;
private final WebAPIManager webAPI;
private final int port;
private boolean enabled = false;
@ -59,7 +50,7 @@ public class WebServer {
public WebServer(IPlan plugin) {
this.plugin = plugin;
this.port = Settings.WEBSERVER_PORT.getNumber();
webAPI = new WebAPIManager();
registerWebAPIs();
}
@ -68,16 +59,16 @@ public class WebServer {
}
private void registerWebAPIs() {
WebAPIManager.registerNewAPI("analytics", new AnalyticsWebAPI());
WebAPIManager.registerNewAPI("analyze", new AnalyzeWebAPI());
WebAPIManager.registerNewAPI("configure", new ConfigureWebAPI());
WebAPIManager.registerNewAPI("inspect", new InspectWebAPI());
WebAPIManager.registerNewAPI("onlineplayers", new OnlinePlayersWebAPI());
WebAPIManager.registerNewAPI("ping", new PingWebAPI());
webAPI.registerNewAPI("analytics", new AnalyticsWebAPI());
webAPI.registerNewAPI("analyze", new AnalyzeWebAPI());
webAPI.registerNewAPI("configure", new ConfigureWebAPI());
webAPI.registerNewAPI("inspect", new InspectWebAPI());
webAPI.registerNewAPI("onlineplayers", new OnlinePlayersWebAPI());
webAPI.registerNewAPI("ping", new PingWebAPI());
}
/**
* Starts up the Webserver in a Asynchronous thread.
* Starts up the WebServer in a new Thread Pool.
*/
public void initServer() {
//Server is already enabled stop code
@ -96,53 +87,7 @@ public class WebServer {
server = HttpServer.create(new InetSocketAddress(port), 10);
}
server.createContext("/", exchange -> {
Headers responseHeaders = exchange.getResponseHeaders();
URI uri = exchange.getRequestURI();
String target = uri.toString();
try {
boolean apiRequest = "POST".equals(exchange.getRequestMethod());
Response response = null;
String type = "text/html;";
if (apiRequest) {
response = getAPIResponse(target, exchange);
if (response instanceof JsonResponse) {
type = "application/json;";
}
}
responseHeaders.set("Content-Type", type);
if (apiRequest) {
sendData(responseHeaders, exchange, response);
return;
}
WebUser user = null;
if (usingHttps) {
user = getUser(exchange.getRequestHeaders());
// Prompt authorization
if (user == null) {
responseHeaders.set("WWW-Authenticate", "Basic realm=\"/\";");
}
}
response = getResponse(target, user);
if (response instanceof CSSResponse) {
responseHeaders.set("Content-Type", "text/css");
}
sendData(responseHeaders, exchange, response);
} catch (Exception e) {
internalErrorResponse(exchange, responseHeaders, target, e);
} finally {
exchange.close();
}
});
server.createContext("/", new RequestHandler(plugin, this));
server.setExecutor(new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100)));
server.start();
@ -156,77 +101,6 @@ public class WebServer {
}
}
private void sendData(Headers header, HttpExchange exchange, Response response) throws IOException {
header.set("Content-Encoding", "gzip");
exchange.sendResponseHeaders(response.getCode(), 0);
try (GZIPOutputStream out = new GZIPOutputStream(exchange.getResponseBody());
ByteArrayInputStream bis = new ByteArrayInputStream(response.getContent().getBytes())) {
byte[] buffer = new byte[2048];
int count;
while ((count = bis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
}
}
private void internalErrorResponse(HttpExchange exchange, Headers responseHeaders, String target, Exception e) {
try {
Log.toLog(target, e);
sendData(responseHeaders, exchange, new InternalErrorResponse(e, target));
} catch (IOException e1) {
Log.toLog(this.getClass().getName(), e1);
}
}
private WebUser getUser(Headers requestHeaders) {
Benchmark.start("getUser");
try {
List<String> authorization = requestHeaders.get("Authorization");
if (Verify.isEmpty(authorization)) {
return null;
}
String auth = authorization.get(0);
if (auth.contains("Basic ")) {
auth = auth.split(" ")[1];
} else {
throw new IllegalArgumentException("Wrong format of Auth");
}
Base64.Decoder decoder = Base64.getDecoder();
byte[] decoded = decoder.decode(auth);
String[] userInfo = new String(decoded).split(":");
if (userInfo.length != 2) {
throw new IllegalArgumentException("User and Password not specified");
}
String user = userInfo[0];
String passwordRaw = userInfo[1];
SecurityTable securityTable = plugin.getDB().getSecurityTable();
if (!securityTable.userExists(user)) {
throw new IllegalArgumentException("User Doesn't exist");
}
WebUser webUser = securityTable.getWebUser(user);
boolean correctPass = PassEncryptUtil.verifyPassword(passwordRaw, webUser.getSaltedPassHash());
if (!correctPass) {
throw new IllegalArgumentException("User and Password do not match");
}
Benchmark.stop("getUser: " + requestHeaders);
return webUser;
} catch (IllegalArgumentException e) {
Log.debug("WebServer: " + e.getMessage());
return null;
} catch (Exception e) {
Log.toLog(this.getClass().getName(), e);
return null;
}
}
private boolean startHttpsServer() {
String keyStorePath = Settings.WEBSERVER_CERTIFICATE_PATH.toString();
if (!Paths.get(keyStorePath).isAbsolute()) {
@ -286,212 +160,6 @@ public class WebServer {
return startSuccessful;
}
private String readPOSTRequest(HttpExchange exchange) throws IOException {
byte[] bytes;
try (InputStream in = exchange.getRequestBody()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
for (int n = in.read(buf); n > 0; n = in.read(buf)) {
out.write(buf, 0, n);
}
bytes = out.toByteArray();
}
try {
return new String(bytes, StandardCharsets.ISO_8859_1);
} catch (Exception e) {
return null;
}
}
private Response getAPIResponse(String target, HttpExchange exchange) throws IOException {
String[] args = target.split("/");
if (args.length < 3) {
String error = "API Method not specified";
return PageCache.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 PageCache.loadPage(error, () -> new BadRequestResponse(error));
}
Map<String, String> variables = readVariables(response);
String key = variables.get("key");
if (!checkKey(key)) {
String error = "Server Key not given or invalid";
return PageCache.loadPage(error, () -> {
ForbiddenResponse forbidden = new ForbiddenResponse();
forbidden.setContent(error);
return forbidden;
});
}
WebAPI api = WebAPIManager.getAPI(method);
if (api == null) {
String error = "API Method not found";
return PageCache.loadPage(error, () -> new BadRequestResponse(error));
}
try {
return api.onResponse(Plan.getInstance(), variables);
} catch (Exception ex) {
Log.toLog("WebServer.getAPIResponse", ex);
return new InternalErrorResponse(ex, "An error while processing the request happened");
}
}
private boolean checkKey(String key) {
if (key == null) {
return false;
}
UUID uuid = Plan.getServerUUID();
UUID keyUUID;
try {
keyUUID = UUID.fromString(key);
} catch (IllegalArgumentException e) {
return false;
}
return uuid.equals(keyUUID);
}
private Map<String, String> readVariables(String response) {
String[] variables = response.split("&");
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) {
if ("/favicon.ico".equals(target)) {
return PageCache.loadPage("Redirect: favicon", () -> new RedirectResponse("https://puu.sh/tK0KL/6aa2ba141b.ico"));
}
if (usingHttps) {
if (user == null) {
return PageCache.loadPage("promptAuthorization", PromptAuthorizationResponse::new);
}
int permLevel = user.getPermLevel(); // Lower number has higher clearance.
int required = getRequiredPermLevel(target, user.getName());
if (permLevel > required) {
return forbiddenResponse(permLevel, required);
}
}
boolean javaScriptRequest = target.endsWith(".js");
boolean cssRequest = target.endsWith(".css");
String[] args = target.split("/");
if (args.length < 2) {
return rootPageResponse(user);
}
if (javaScriptRequest) {
return getJSResponse(args[args.length - 1]);
}
if (cssRequest) {
try {
return new CSSResponse("main.css");
} catch (Exception e) {
return new InternalErrorResponse(e, target);
}
}
String page = args[1];
switch (page) {
case "players":
return PageCache.loadPage("players", PlayersPageResponse::new);
case "player":
return playerResponse(args);
case "server":
return serverResponse();
default:
return notFoundResponse();
}
}
private Response getJSResponse(String fileName) {
try {
return new JavaScriptResponse(fileName);
} catch (Exception e) {
return new InternalErrorResponse(e, fileName);
}
}
private Response forbiddenResponse(int permLevel, int required) {
return PageCache.loadPage("forbidden", () ->
new ForbiddenResponse("Unauthorized User.<br>"
+ "Make sure your user has the correct access level.<br>"
+ "This page requires permission level of " + required + ",<br>"
+ "This user has permission level of " + permLevel));
}
private Response rootPageResponse(WebUser user) {
if (user == null) {
return notFoundResponse();
}
switch (user.getPermLevel()) {
case 0:
return serverResponse();
case 1:
return PageCache.loadPage("players", PlayersPageResponse::new);
case 2:
return playerResponse(new String[]{"", "", user.getName()});
default:
return forbiddenResponse(user.getPermLevel(), 0);
}
}
private Response serverResponse() {
if (!infoManager.isAnalysisCached()) {
String error = "Analysis Data was not cached.<br>Use /plan analyze to cache the Data.";
PageCache.loadPage("notFound: " + error, () -> new NotFoundResponse(error));
}
return PageCache.loadPage("analysisPage", () -> new AnalysisPageResponse(infoManager));
}
private Response playerResponse(String[] args) {
if (args.length < 3) {
return PageCache.loadPage("notFound", NotFoundResponse::new);
}
String playerName = args[2].trim();
UUID uuid = UUIDUtility.getUUIDOf(playerName);
if (uuid == null) {
String error = "Player has no UUID";
return PageCache.loadPage("notFound: " + error, () -> new NotFoundResponse(error));
}
plugin.getInfoManager().cachePlayer(uuid);
return PageCache.loadPage("inspectPage: " + uuid, () -> new InspectPageResponse(infoManager, uuid));
}
private Response notFoundResponse() {
String error = "404 Not Found";
return PageCache.loadPage("notFound: " + error, () ->
new NotFoundResponse("Make sure you're accessing a link given by a command, Examples:</p>"
+ "<p>" + getProtocol() + ":" + HtmlUtils.getInspectUrl("<player>") + " or<br>"
+ getProtocol() + ":" + HtmlUtils.getServerAnalysisUrl())
);
}
/**
* @return if the WebServer is enabled
*/
@ -509,33 +177,6 @@ public class WebServer {
}
}
private int getRequiredPermLevel(String target, String user) {
String[] t = target.split("/");
if (t.length < 2) {
return 100;
}
if (t.length > 3) {
return 0;
}
String page = t[1];
switch (page) {
case "players":
return 1;
case "player":
// /player/ - 404 for perm lvl 1
if (t.length < 3) {
return 1;
}
final String wantedUser = t[2].toLowerCase().trim();
final String theUser = user.trim().toLowerCase();
return wantedUser.equals(theUser) ? 2 : 1;
default:
return 0;
}
}
public String getProtocol() {
return usingHttps ? "https" : "http";
}
@ -551,4 +192,8 @@ public class WebServer {
public String getAccessAddress() {
return getProtocol() + ":/" + HtmlUtils.getIP();
}
public WebAPIManager getWebAPI() {
return webAPI;
}
}

View File

@ -10,6 +10,7 @@ public class CSSResponse extends FileResponse {
public CSSResponse(String fileName) {
super(fileName);
super.setType(ResponseType.CSS);
setContent(Theme.replaceColors(getContent()));
}
}

View File

@ -8,5 +8,6 @@ public class JavaScriptResponse extends FileResponse {
public JavaScriptResponse(String fileName) {
super(fileName);
super.setType(ResponseType.JAVASCRIPT);
}
}

View File

@ -1,6 +1,12 @@
package main.java.com.djrapitops.plan.systems.webserver.response;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;
/**
* @author Rsl1122
@ -8,13 +14,21 @@ import java.util.Objects;
*/
public abstract class Response {
private String type;
private String header;
private String content;
private Headers responseHeaders;
/**
* Class Constructor.
*/
public Response(ResponseType type) {
this.type = type.get();
}
public Response() {
this.type = ResponseType.HTML.get();
}
public String getResponse() {
@ -54,4 +68,27 @@ public abstract class Response {
public int hashCode() {
return Objects.hash(header, content);
}
protected void setType(ResponseType type) {
this.type = type.get();
}
public void setResponseHeaders(Headers responseHeaders) {
this.responseHeaders = responseHeaders;
}
public void send(HttpExchange exchange) throws IOException {
responseHeaders.set("Content-Type", type);
responseHeaders.set("Content-Encoding", "gzip");
exchange.sendResponseHeaders(getCode(), 0);
try (GZIPOutputStream out = new GZIPOutputStream(exchange.getResponseBody());
ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes())) {
byte[] buffer = new byte[2048];
int count;
while ((count = bis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
}
}
}

View File

@ -0,0 +1,27 @@
/*
* 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.systems.webserver.response;
/**
* //TODO Class Javadoc Comment
*
* @author Rsl1122
*/
public enum ResponseType {
HTML("text/html"),
CSS("text/css"),
JSON("application/json"),
JAVASCRIPT("application/javascript");
private final String type;
ResponseType(String type) {
this.type = type;
}
public String get() {
return type;
}
}

View File

@ -6,6 +6,7 @@ package main.java.com.djrapitops.plan.systems.webserver.response.api;
import com.google.gson.Gson;
import main.java.com.djrapitops.plan.systems.webserver.response.Response;
import main.java.com.djrapitops.plan.systems.webserver.response.ResponseType;
/**
* @author Fuzzlemann
@ -13,6 +14,7 @@ import main.java.com.djrapitops.plan.systems.webserver.response.Response;
public class JsonResponse extends Response {
public <T> JsonResponse(T object) {
super(ResponseType.JSON);
Gson gson = new Gson();
super.setHeader("HTTP/1.1 200 OK");