Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/de/igslandstuhl/database/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private Server() {
String keystorePath = Application.getInstance().runsWebServer() ? Application.getInstance().getOptionSafe("keystore", "keys/web/keystore.jks") : null;
String keystorePassword = Application.getInstance().runsWebServer() ? Application.getInstance().getOptionSafe("keystore-password", "changeit") : null;
int port = 443;
webServer = Application.getInstance().runsWebServer() ? new WebServer(port, keystorePath, keystorePassword) : null;
webServer = Application.getInstance().runsWebServer() ? new WebServer(port, keystorePath, keystorePassword) : new WebServer();
} catch (Exception e) {
throw new IllegalStateException("Server failed on start", e);
}
Expand Down
40 changes: 30 additions & 10 deletions src/main/java/de/igslandstuhl/database/server/WebServer.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.igslandstuhl.database.server;

import java.io.*;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

Expand All @@ -21,23 +23,27 @@

import de.igslandstuhl.database.server.webserver.GetRequest;
import de.igslandstuhl.database.server.webserver.GetResponse;
import de.igslandstuhl.database.server.webserver.PostHeader;
import de.igslandstuhl.database.server.webserver.HttpHeader;
import de.igslandstuhl.database.server.webserver.PostRequest;
import de.igslandstuhl.database.server.webserver.PostRequestHandler;
import de.igslandstuhl.database.server.webserver.PostResponse;
import de.igslandstuhl.database.server.webserver.UserManager;
import de.igslandstuhl.database.server.webserver.SessionManager;

/**
* A simple HTTPS web server that handles various requests related to student data.
* It supports GET and POST requests for login, subject requests, current topics, tasks, and room updates.
*/
public class WebServer implements Runnable {
public static final int SESSION_DURATION = 21600; // six hours
public static final int MAXIMUM_INACTIVITY_DURATION = 60; // An hour

private volatile boolean running;
private final SSLServerSocket serverSocket;
private final UserManager userManager = new UserManager();
private final SessionManager userManager = new SessionManager(SESSION_DURATION, MAXIMUM_INACTIVITY_DURATION);
private final ExecutorService clientPool = Executors.newCachedThreadPool();
private final boolean secure = true;

public UserManager getUserManager() {
public SessionManager getSessionManager() {
return userManager;
}

Expand All @@ -64,9 +70,17 @@ protected WebServer() {

class ClientHandler implements Runnable {
private final SSLSocket clientSocket;
private final String clientIp;

ClientHandler(SSLSocket socket) {
this.clientSocket = socket;
InetAddress inetAddress;
try {
inetAddress = socket != null ? socket.getInetAddress() : InetAddress.getLocalHost();
} catch (UnknownHostException e) {
inetAddress = null;
}
clientIp = inetAddress != null ? inetAddress.getHostAddress() : null;
}

@Override
Expand Down Expand Up @@ -105,15 +119,21 @@ String readHeadersAsString(InputStream in) throws IOException {
}

void handleGet(String headerString, PrintStream out) {
String user = Server.getInstance().getWebServer().getUserManager().getSessionUser(headerString);
GetRequest get = new GetRequest(headerString);
GetResponse response = GetResponse.getResource(get.toResourceLocation(user), user);
SessionManager sessionManager = Server.getInstance().getWebServer().getSessionManager();
GetRequest get = new GetRequest(headerString, clientIp, secure);
GetResponse response;
if (!sessionManager.validateSession(get)) {
response = GetResponse.internalServerError();
} else {
String user = sessionManager.getSessionUser(get).getUsername();
response = GetResponse.getResource(get.toResourceLocation(user), user);
}
response.respond(out);
}

void handlePost(String headerString, InputStream in, PrintStream out) throws IOException {
Map<String, String> headerMap = parseHeaders(headerString);
PostHeader postHeader = new PostHeader(headerString);
HttpHeader postHeader = new HttpHeader(headerString);
int contentLength = headerMap.containsKey("content-length") ? Integer.parseInt(headerMap.get("content-length")) : 0;
Charset bodyCharset = determineCharset(headerMap.get("content-type"));
String body = null;
Expand All @@ -122,8 +142,8 @@ void handlePost(String headerString, InputStream in, PrintStream out) throws IOE
String raw = new String(bodyBytes, bodyCharset);
body = URLDecoder.decode(raw, bodyCharset.name());
}
PostRequest parsedRequest = new PostRequest(postHeader, body);
PostResponse response = PostRequestHandler.getInstance().handlePostRequest(parsedRequest);
PostRequest parsedRequest = new PostRequest(postHeader, body, clientIp, secure);
PostResponse response = Server.getInstance().getWebServer().getSessionManager().validateSession(parsedRequest) ? PostRequestHandler.getInstance().handlePostRequest(parsedRequest) : PostResponse.badRequest("Bad request: session manipulation", parsedRequest);
response.respond(out);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* It parses the request string to extract the path, query parameters, and context.
* The context is determined by the file extension of the requested resource.
*/
public class GetRequest {
public class GetRequest implements HttpRequest {
/**
* Valid contexts for web resources.
* These contexts are used to determine the type of resource being requested.
Expand All @@ -34,19 +34,26 @@ public class GetRequest {
*/
private final String context;

private final int contentLength;
private final Cookie[] cookies;
private final String ip;
private final String userAgent;
private final String acceptLanguage;
private final boolean secureConnection;

/**
* Constructs a new GetRequest from the given request string.
* @param request the request string to parse
*/
public GetRequest(String request) {
public GetRequest(String request, String ip, boolean secureConnection) {
if (!request.startsWith("GET") || !request.contains("HTTP/1.1")){
throw new IllegalArgumentException();
}
String [] lines = request.split("\n");
HttpHeader header = new HttpHeader(request);

String url = lines[0].split(" ")[1];
String url = header.getPath();
String[] urlParts = url.split("\\?");
path = urlParts[0];
this.path = urlParts[0];
if (urlParts.length > 1) {
String[] arg_pairs = urlParts[1].split("&");
for (String arg: arg_pairs) {
Expand All @@ -56,10 +63,17 @@ public GetRequest(String request) {
}
String[] extPts = path.split("\\.");
if (extPts.length > 1) {
context = extPts[1];
this.context = extPts[1];
} else {
context = "html";
this.context = "html";
}

this.contentLength = 0;
this.ip = ip;
this.userAgent = header.getUserAgent();
this.acceptLanguage = header.getAcceptLanguage();
this.secureConnection = secureConnection;
this.cookies = header.getCookies();
}

/**
Expand All @@ -80,4 +94,38 @@ public boolean isValid() {
public ResourceLocation toResourceLocation(String user) {
return WebResourceHandler.locationFromPath(path, User.getUser(user));
}

@Override
public String getPath() {
return path;
}
@Override
public String getContext() {
return context;
}
@Override
public int getContentLength() {
return contentLength;
}
@Override
public Cookie[] getCookies() {
return cookies;
}
@Override
public String getIP() {
return ip;
}
@Override
public String getUserAgent() {
return userAgent;
}
@Override
public String getAcceptLanguage() {
return acceptLanguage;
}
@Override
public boolean isSecureConnection() {
return secureConnection;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.ArrayList;
import java.util.List;

public class PostHeader {
public class HttpHeader {
/**
* Represents the path of the POST request.
* This is the part of the request line that specifies the resource being requested.
Expand All @@ -19,19 +19,23 @@ public class PostHeader {
* This is an array of Cookie objects that may be included in the request.
*/
private final Cookie[] cookies;
private final String userAgent;
private final String acceptLanguage;

/**
* Constructs a new PostRequest with the given header and body.
* @param header the header of the POST request
* @param body the body of the POST request
*/
public PostHeader(String header) {
public HttpHeader(String header) {
// Example: "POST /login HTTP/1.1"
String[] parts = header.split(" ");
this.path = parts[1];

int contentLength = 0;
Cookie[] cookies = new Cookie[0];
String userAgent = null;
String acceptLanguage = null;
for (String line : header.split("\n")) {
if (line.startsWith("Content-Length:")) {
contentLength = Integer.parseInt(line.split(":")[1].trim());
Expand All @@ -45,10 +49,16 @@ public PostHeader(String header) {
}
}
cookies = cookieList.toArray(new Cookie[0]);
} else if (line.startsWith("User-Agent:")) {
userAgent = line.split(":")[1].trim();
} else if (line.startsWith("Accept-Language:")) {
acceptLanguage = line.split(":")[1].trim();
}
}
this.cookies = cookies;
this.contentLength = contentLength;
this.userAgent = userAgent;
this.acceptLanguage = acceptLanguage;
}

/**
Expand All @@ -71,4 +81,12 @@ public int getContentLength() {
public Cookie[] getCookies() {
return cookies;
}

public String getUserAgent() {
return userAgent;
}

public String getAcceptLanguage() {
return acceptLanguage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.igslandstuhl.database.server.webserver;

public interface HttpRequest {
/**
* Returns the path of the HTTP request.
* This is the part of the request line that specifies the resource being requested.
* @return the path of the HTTP request
*/
public String getPath();
/**
* Returns the context of the HTTP request.
* This is derived from the path, typically indicating the type of resource (e.g., "html", "json").
* @return the context of the HTTP request
*/
public String getContext();
/**
* Returns the content length of the HTTP request.
* This is used to determine the size of the request body.
* @return the content length of the HTTP request
*/
public int getContentLength();
/**
* Returns the cookies already set in the browser.
* This is used for session storage for example.
* @return the cookies
*/
public Cookie[] getCookies();
public String getIP();
public String getUserAgent();
public String getAcceptLanguage();
public boolean isSecureConnection();
}
Loading
Loading