package org.openslx.bwlp.sat.web; import java.io.ByteArrayInputStream; import java.io.IOException; import java.sql.SQLException; import java.util.Map; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.log4j.Logger; import org.openslx.bwlp.sat.database.mappers.DbLecture; import org.openslx.bwlp.sat.fileserv.FileServer; import org.openslx.bwlp.thrift.iface.LectureRead; import org.openslx.bwlp.thrift.iface.NetRule; import org.openslx.bwlp.thrift.iface.TNotFoundException; import org.openslx.util.Json; import org.openslx.util.vm.VmMetaData; import org.simpleframework.xml.Serializer; import org.simpleframework.xml.core.Persister; import fi.iki.elonen.NanoHTTPD; public class WebServer extends NanoHTTPD { private static final Logger LOGGER = Logger.getLogger(WebServer.class); private static final Serializer serializer = new Persister(); public WebServer(int port) { super("127.0.0.1", port); super.maxRequestSize = 65535; } /** * Extract request source ip address. Honors the x-forwarded-for header. * * @param headers * map of headers as supplied by nanohttpd * @return IP address, or empty string if unknown */ private String extractIp(Map headers) { String ip; ip = headers.get("remote-addr"); if (ip != null && !ip.equals("127.0.0.1")) return ip; if (headers == null || headers.isEmpty()) return ""; ip = headers.get("x-forwarded-for"); if (ip == null || ip.isEmpty()) return ""; final int i = ip.lastIndexOf(','); if (i == -1) return ip.trim(); return ip.substring(i + 1).trim(); } @Override public Response serve(IHTTPSession session) { String uri = session.getUri(); if (uri == null || uri.length() == 0) { return internalServerError(); } // Sanitize if (uri.contains("//")) { uri = uri.replaceAll("//+", "/"); } try { return handle(session, uri); } catch (Throwable t) { LOGGER.debug("Could not handle request", t); return internalServerError(); } } private Response handle(IHTTPSession session, String uri) { // Our special stuff String[] parts = uri.replaceFirst("^/+", "").split("/+"); // /vmchooser/* if (parts.length > 1 && parts[0].equals("vmchooser")) { if (parts[1].equals("list")) { try { return serveVmChooserList(session.getParms()); } catch (Exception e) { LOGGER.debug("problem while retrieving the vmChooserList", e); return internalServerError(); } } if (parts[1].equals("lecture")) { if (parts.length < 3) return badRequest("Bad Request"); if (parts.length < 4 || parts[3].equals("vmx")) return serveLectureStart(parts[2]); if (parts[3].equals("netrules")) return serveLectureNetRules(parts[2]); if (parts[3].equals("runscript")) return serveLectureScript(parts[2]); } return notFound(); } if (uri.startsWith("/status/fileserver")) { return serveStatus(); } if (session.getMethod() == Method.POST && uri.startsWith("/do/")) { try { session.parseBody(null); } catch (IOException | ResponseException e) { LOGGER.debug("could not parse request body", e); return internalServerError(); } return WebRpc.handle(uri.substring(4), session.getParms()); } return notFound(); } private Response serveStatus() { return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/json; charset=utf-8", Json.serialize(FileServer.instance().getStatus())); } /** * Return meta data (eg. *.vmx) required to start the given lecture. * * @param lectureId * @return */ private Response serveLectureStart(String lectureId) { VmMetaData meta; try { meta = DbLecture.getClientLaunchData(lectureId); } catch (TNotFoundException e) { return notFound(); } catch (SQLException e) { return internalServerError(); } return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", new ByteArrayInputStream(meta.getFilteredDefinitionArray())); } private Response serveLectureNetRules(String lectureId) { LectureRead lecture; try { lecture = DbLecture.getLectureDetails(null, lectureId); } catch (TNotFoundException e) { return notFound(); } catch (SQLException e) { return internalServerError(); } StringBuilder sb = new StringBuilder(); if (lecture.networkExceptions != null) { for (NetRule rule : lecture.networkExceptions) { sb.append(rule.direction.name()); sb.append(' '); sb.append(rule.host); sb.append(' '); sb.append(rule.port); sb.append(' '); sb.append(lecture.hasInternetAccess ? "REJECT" : "ACCEPT"); sb.append('\n'); } } if (lecture.hasInternetAccess) { sb.append("IN * 0 ACCEPT\n"); sb.append("OUT * 0 ACCEPT\n"); } else { sb.append("IN * 0 REJECT\n"); sb.append("OUT * 0 REJECT\n"); } return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", sb.toString()); } private Response serveLectureScript(String lectureId) { LectureRead lecture; try { lecture = DbLecture.getLectureDetails(null, lectureId); } catch (TNotFoundException e) { return notFound(); } catch (SQLException e) { return internalServerError(); } if (lecture.runscript == null) { lecture.runscript = ""; } return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8", lecture.runscript); } /** * Return full list of lectures matching given location(s). * * @return * @throws Exception */ private Response serveVmChooserList(Map params) throws Exception { String locations = params.get("locations"); boolean exams = params.containsKey("exams"); VmChooserListXml listXml = DbLecture.getUsableListXml(exams, locations); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.write(listXml, baos); return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/xml; charset=utf-8", new ByteArrayInputStream(baos.toByteArray())); } /** * Helper for returning "Internal Server Error" Status */ public static Response internalServerError() { return new NanoHTTPD.Response(NanoHTTPD.Response.Status.INTERNAL_ERROR, "text/plain", "Internal Server Error"); } /** * Helper for returning "404 Not Found" Status */ public static Response notFound() { return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", "Nicht gefunden!"); } /** * Helper for returning "Bad Request" Status */ public static Response badRequest(String message) { if (message == null) { message = "Schlechte Anfrage!"; } return new NanoHTTPD.Response(NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", message); } }