More request validation and boilerplate code; now the request URI is validated against the request body's contents, and we are parsing the request into an XML document

This commit is contained in:
skogaby 2019-01-04 01:10:00 -06:00
parent 4d504ce04e
commit 872b4d97c5
6 changed files with 151 additions and 26 deletions

View File

@ -17,6 +17,7 @@ dependencies {
compile group: 'com.sparkjava', name: 'spark-core', version: '2.7.2'
compile group: 'com.google.guava', name: 'guava', version: '23.5-jre'
compile group: 'org.slf4j', name: 'slf4j-simple', version:'1.7.21'
}
jar {

View File

@ -2,18 +2,22 @@ package com.buttongames.butterfly.http;
import com.buttongames.butterfly.compression.Lz77;
import com.buttongames.butterfly.encryption.Rc4;
import com.buttongames.butterfly.http.exception.InvalidRequestException;
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
import com.buttongames.butterfly.http.exception.InvalidRequestModelException;
import com.buttongames.butterfly.http.exception.InvalidRequestModuleException;
import com.buttongames.butterfly.http.exception.MismatchedRequestUriException;
import com.buttongames.butterfly.http.handlers.ServicesRequestHandler;
import com.buttongames.butterfly.xml.BinaryXmlUtils;
import com.buttongames.butterfly.xml.XmlUtils;
import com.google.common.collect.ImmutableSet;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import spark.Request;
import spark.utils.StringUtils;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import static spark.Spark.exception;
@ -62,11 +66,16 @@ public class ButterflyHttpServer {
SUPPORTED_MODULES = ImmutableSet.of("services");
}
/**
* Handler for requests for the <code>services<code> module.
*/
private ServicesRequestHandler servicesRequestHandler;
/**
* Constructor.
*/
public ButterflyHttpServer() {
this.servicesRequestHandler = new ServicesRequestHandler();
}
/**
@ -96,12 +105,11 @@ public class ButterflyHttpServer {
// configure our root route; its handler will parse the request and go from there
post("/", ((request, response) -> {
// send the request to the right module handler
final String requestBody = validateAndUnpackRequest(request);
final Element requestBody = validateAndUnpackRequest(request);
final String requestModule = request.queryParams("module");
final String requestMethod = request.queryParams("method");
if (requestModule.equals("services")) {
return ServicesRequestHandler.handleRequest(requestBody, requestMethod, response);
return this.servicesRequestHandler.handleRequest(requestBody, request, response);
} else {
throw new InvalidRequestModuleException();
}
@ -119,13 +127,17 @@ public class ButterflyHttpServer {
}
/**
* Do some basic validation on the request before we handle it. Returns the request
* body in plaintext form for handling, if it was a valid request.
* TODO: Remove all the hardcoded stuff.
* @param request The request to validate and unpack
* @return A string representing the plaintext version of the packet, in XML format.
* Validates incoming requests for basic sanity checks, and returns the request
* as a plaintext XML document.
* @param request The incoming request.
* @return An <code>Element</code> representing the root of the request document
* @throws GeneralSecurityException
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
private String validateAndUnpackRequest(Request request) throws GeneralSecurityException, IOException {
private Element validateAndUnpackRequest(Request request)
throws GeneralSecurityException, IOException, ParserConfigurationException, SAXException {
final String requestUriModel = request.queryParams("model");
final String requestUriModule = request.queryParams("module");
final String requestUriMethod = request.queryParams("method");
@ -163,9 +175,30 @@ public class ButterflyHttpServer {
// read the request body into an XML document and check its properties
// to verify it matches the request URI
// TODO: Implement
final Element rootNode = XmlUtils.byteArrayToXmlFile(reqBody);
if (rootNode == null ||
!rootNode.getNodeName().equals("call")) {
throw new InvalidRequestException();
}
final Element moduleNode = (Element) rootNode.getFirstChild();
final String requestBodyModel = rootNode.getAttribute("model");
final String requestBodyModule = moduleNode.getNodeName();
final String requestBodyMethod = moduleNode.getAttribute("method");
if (StringUtils.isBlank(requestBodyModel) ||
StringUtils.isBlank(requestBodyModule) ||
StringUtils.isBlank(requestBodyMethod) ||
!requestBodyModel.equals(requestUriModel) ||
!requestBodyModule.equals(requestUriModule) ||
!requestBodyMethod.equals(requestUriMethod)) {
throw new MismatchedRequestUriException();
}
// TODO: Verify the PCBID
// 4) return the XML document
return new String(reqBody, StandardCharsets.UTF_8);
return rootNode;
}
}

View File

@ -0,0 +1,9 @@
package com.buttongames.butterfly.http.exception;
/**
* Exception thrown when a request is made with some bad property (catch-all exception for
* when we run out of specific ones but have an issue).
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class InvalidRequestException extends RuntimeException {
}

View File

@ -1,8 +1,21 @@
package com.buttongames.butterfly.http.handlers;
import org.w3c.dom.Element;
import spark.Request;
import spark.Response;
/**
* Base request handler that the others inherit from.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class BaseRequestHandler {
public abstract class BaseRequestHandler {
/**
* Handles an incoming request for the given module.
* @param requestBody The XML document of the incoming request.
* @param request The Spark request
* @param response The Spark response
* @return A response object for Spark
*/
public abstract Object handleRequest(final Element requestBody, final Request request, final Response response);
}

View File

@ -1,6 +1,8 @@
package com.buttongames.butterfly.http.handlers;
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
import org.w3c.dom.Element;
import spark.Request;
import spark.Response;
/**
@ -10,27 +12,30 @@ import spark.Response;
public class ServicesRequestHandler extends BaseRequestHandler {
/**
* Handles all requests to the <code>services/code> module.
* @param request
* @param requestMethod
* @param response
* @return
* Handles an incoming request for the services module.
* @param requestBody The XML document of the incoming request.
* @param request The Spark request
* @param response The Spark response
* @return A response object for Spark
*/
public static Object handleRequest(String request, String requestMethod, Response response) {
@Override
public Object handleRequest(final Element requestBody, final Request request, final Response response) {
final String requestMethod = request.queryParams("method");
if (requestMethod.equals("get")) {
return handleGetRequest(request, response);
return handleGetRequest(request, response);
} else {
throw new InvalidRequestMethodException();
}
}
/**
* Handles <code>services.get</code> requests.
* @param request
* @param response
* @return
* Handles an incoming request for the given module.
* @param request The Spark request
* @param response The Spark response
* @return A response object for Spark
*/
private static Object handleGetRequest(String request, Response response) {
private static Object handleGetRequest(final Request request, final Response response) {
// TODO: Implement
return "Womp womp";
}

View File

@ -0,0 +1,64 @@
package com.buttongames.butterfly.xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* Class with helper methods for manipulating XML files.
* @author skogaby (skogabyskogaby@gmail.com)
*/
public class XmlUtils {
/**
* Scrubs empty nodes from a document so we don't accidentally read them.
* @param node The root node of the document to clean.
*/
public static void clean(final Node node) {
final NodeList childrem = node.getChildNodes();
for (int n = childrem.getLength() - 1; n >= 0; n--) {
final Node child = childrem.item(n);
final short nodeType = child.getNodeType();
if (nodeType == Node.ELEMENT_NODE) {
clean(child);
} else if (nodeType == Node.TEXT_NODE) {
final String trimmedNodeVal = child.getNodeValue().trim();
if (trimmedNodeVal.length() == 0) {
node.removeChild(child);
} else {
child.setNodeValue(trimmedNodeVal);
}
} else if (nodeType == Node.COMMENT_NODE) {
node.removeChild(child);
}
}
}
/**
* Reads the given byte[] into an Element that represents the root node of the XML body.
* @param body
* @return
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public static Element byteArrayToXmlFile(final byte[] body)
throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = builderFactory.newDocumentBuilder();
final Document reqDocument = builder.parse(new ByteArrayInputStream(body));
XmlUtils.clean(reqDocument);
return reqDocument.getDocumentElement();
}
}