/**
* **************************************************************************
*
* Contributor(s):
* C. Heazel (WiSC): Added Fortify adjudication changes
*
***************************************************************************
*/
package com.occamlab.te.web;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.XMLConstants; // Addition for Fortify modifications
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.s9api.XdmNode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.occamlab.te.TECore;
import com.occamlab.te.util.DomUtils;
/**
* A servlet that intercepts a client request and validates it with a registered
* MonitorCall object. Each MonitorCall object is associated with a service
* endpoint.
*
*/
@SuppressWarnings("serial")
public class MonitorServlet extends HttpServlet {
private static final Logger LOGR = Logger.getLogger(MonitorServlet.class
.getPackage().getName());
public static final String CTL_NS = "http://www.occamlab.com/ctl";
static DocumentBuilder DB;
static Transformer identityTransformer;
static String baseServletURL;
static String servletName;
static int monitorCallSeq = 0;
static int monitorUrlSeq = 0;
static Map<String, MonitorCall> monitors = new HashMap<String, MonitorCall>();
static public String allocateMonitorUrl(String url) {
String monitorUrl = baseServletURL + "/" + servletName + "/"
+ Integer.toString(monitorUrlSeq);
monitorUrlSeq++;
MonitorCall mc = new MonitorCall(url);
monitors.put(monitorUrl, mc);
return monitorUrl;
}
// Monitor without parser that doesn't trigger a test
static public String createMonitor(String monitorUrl, TECore core) {
return createMonitor(monitorUrl, null, "", core);
}
// Monitor that doesn't trigger a test
static public String createMonitor(String monitorUrl,
Node parserInstruction, String modifiesResponse, TECore core) {
MonitorCall mc = monitors.get(monitorUrl);
mc.setCore(core);
if (parserInstruction != null) {
mc.setParserInstruction(DomUtils.getElement(parserInstruction));
mc.setModifiesResponse(Boolean.parseBoolean(modifiesResponse));
}
LOGR.log(Level.CONFIG, "Configured monitor without test:\n {0}", mc);
return "";
}
// Monitor without parser that triggers a test
static public String createMonitor(XPathContext context, String url,
String localName, String namespaceURI, NodeInfo params,
String callId, TECore core) throws Exception {
return createMonitor(context, url, localName, namespaceURI, params,
null, "", callId, core);
}
// Monitor that triggers a test
static public String createMonitor(XPathContext context, String monitorUrl,
String localName, String namespaceURI, NodeInfo params,
NodeInfo parserInstruction, String modifiesResponse, String callId,
TECore core) throws Exception {
MonitorCall mc = monitors.get(monitorUrl);
mc.setContext(context);
mc.setLocalName(localName);
mc.setNamespaceURI(namespaceURI);
mc.setCore(core);
if (params != null) {
Node node = (Node) NodeOverNodeInfo.wrap(params);
if (node.getNodeType() == Node.DOCUMENT_NODE) {
mc.setParams(((Document) node).getDocumentElement());
} else {
mc.setParams((Element) node);
}
}
if (parserInstruction != null) {
Node node = (Node) NodeOverNodeInfo.wrap(parserInstruction);
if (node.getNodeType() == Node.DOCUMENT_NODE) {
mc.setParserInstruction(((Document) node).getDocumentElement());
} else {
mc.setParserInstruction((Element) node);
}
mc.setModifiesResponse(Boolean.parseBoolean(modifiesResponse));
}
mc.setCallId(callId);
LOGR.log(Level.CONFIG, "Configured monitor with test:\n {0}", mc);
return "";
}
public static String destroyMonitors(TECore core) {
ArrayList<String> keysToDelete = new ArrayList<String>();
for (Entry<String, MonitorCall> entry : monitors.entrySet()) {
MonitorCall mc = entry.getValue();
if (mc.getCore() == core) {
if (mc.getTestPath().equals(core.getTestPath())) {
keysToDelete.add(entry.getKey());
mc.destroy();
}
}
}
for (String key : keysToDelete) {
monitors.remove(key);
}
return "";
}
public void process(HttpServletRequest request,
HttpServletResponse response, boolean post) throws ServletException {
try {
String uri = request.getRequestURL().toString();
MonitorCall mc = monitors.get(uri);
if (mc == null) {
response.sendError(410, "This URL is no longer valid");
return;
}
if (null == request.getContentType()) {
// check GET requests only
String query = null;
query = URLDecoder.decode(request.getQueryString(), "UTF-8");
mc.checkCoverage(query);
}
TECore core = mc.getCore();
String url = mc.getUrl();
String queryString = request.getQueryString();
if (queryString != null) {
if (url.contains("?")) {
url += queryString;
} else {
url += "?" + queryString;
}
}
LOGR.log(Level.FINE, "Opening connection to " + url);
HttpURLConnection huc = (HttpURLConnection) (new URL(url)
.openConnection());
CachedHttpURLConnection uc = new CachedHttpURLConnection(huc);
String method = request.getMethod();
uc.setRequestMethod(method);
uc.setDoInput(true);
uc.setDoOutput(post);
byte[] data = null;
if (post) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy_stream(request.getInputStream(), baos);
data = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(data);
copy_stream(bais, uc.getOutputStream());
}
Document doc = DB.newDocument();
Element eRequest = encodeRequest(request, doc, data);
Element parserInstruction = mc.getParserInstruction();
Element eResponse = core.parse(uc, parserInstruction);
Map<String, List<String>> responseHeaders = uc.getHeaderFields();
for (Entry<String, List<String>> entry : responseHeaders.entrySet()) {
String key = entry.getKey();
// System.out.println(key + ": " + entry.getValue());
if (key != null) {
if (key.length() == 0) {
// do nothing
} else if (key.equalsIgnoreCase("Transfer-Encoding")) {
// do nothing
} else {
for (String value : entry.getValue()) {
response.setHeader(key, value);
}
}
}
}
if (mc.getModifiesResponse()) {
LOGR.log(Level.FINE, DomUtils.serializeNode(eResponse));
Element content = DomUtils.getElementByTagName(eResponse,
"content");
Element root = DomUtils.getChildElement(content);
identityTransformer.transform(new DOMSource(root),
new StreamResult(response.getOutputStream()));
} else {
response.setContentLength(uc.getLength());
copy_stream(uc.getInputStream(), response.getOutputStream());
}
if (mc.getCallId() != null) {
identityTransformer.transform(new DOMSource(mc.getParams()),
new DOMResult(doc));
Element eParams = DomUtils.getElementByTagName(doc, "params");
Element eReqParam = doc.createElement("param");
eReqParam.setAttribute("local-name", "request");
eReqParam.setAttribute("namespace-uri", "");
eReqParam.setAttribute("prefix", "");
eReqParam.setAttribute("type", "node()");
Element eReqValue = doc.createElement("value");
eReqValue.appendChild(eRequest);
eReqParam.appendChild(eReqValue);
eParams.appendChild(eReqParam);
Element eRespParam = doc.createElement("param");
eRespParam.setAttribute("local-name", "response");
eRespParam.setAttribute("namespace-uri", "");
eRespParam.setAttribute("prefix", "");
eRespParam.setAttribute("type", "node()");
Element eRespValue = doc.createElement("value");
identityTransformer.transform(new DOMSource(eResponse),
new DOMResult(eRespValue));
eRespParam.appendChild(eRespValue);
eParams.appendChild(eRespParam);
net.sf.saxon.s9api.DocumentBuilder builder = core.getEngine()
.getBuilder();
XdmNode paramsNode = builder.build(new DOMSource(doc));
monitorCallSeq++;
String callId = mc.getCallId() + "_"
+ Integer.toString(monitorCallSeq);
core.callTest(mc.getContext(), mc.getLocalName(),
mc.getNamespaceURI(), paramsNode.getUnderlyingNode(),
callId);
}
} catch (Throwable t) {
throw new ServletException(t);
}
}
public void init() throws ServletException {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
// Fortify Mod: prevent external entity injection
dbf.setExpandEntityReferences(false);
DB = dbf.newDocumentBuilder();
// Fortify Mod: prevent external entity injection
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
identityTransformer = tf.newTransformer();
// identityTransformer = TransformerFactory.newInstance().newTransformer();
// End Fortify Mod
servletName = this.getServletName();
} catch (Exception e) {
throw new ServletException(e);
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
process(request, response, false);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
process(request, response, true);
}
@SuppressWarnings("unchecked")
Element encodeRequest(HttpServletRequest request, Document doc, byte[] data)
throws Exception {
Element eRequest = doc.createElementNS(CTL_NS, "ctl:request");
Element eURL = doc.createElementNS(CTL_NS, "ctl:url");
eURL.setTextContent(request.getRequestURL().toString());
eRequest.appendChild(eURL);
Element eMethod = doc.createElementNS(CTL_NS, "ctl:method");
eMethod.setTextContent(request.getMethod());
eRequest.appendChild(eMethod);
Enumeration<String> requestHeaders = request.getHeaderNames();
while (requestHeaders.hasMoreElements()) {
String key = (String) requestHeaders.nextElement();
Element eHeader = doc.createElementNS(CTL_NS, "ctl:header");
eHeader.setAttribute("name", key);
eHeader.setTextContent(request.getHeader(key));
eRequest.appendChild(eHeader);
}
Enumeration<String> params = request.getParameterNames();
while (params.hasMoreElements()) {
String key = (String) params.nextElement();
Element eParam = doc.createElementNS(CTL_NS, "ctl:param");
eParam.setAttribute("name", key);
eParam.setTextContent(request.getParameter(key));
eRequest.appendChild(eParam);
}
if (data != null) {
String mime = request.getContentType();
if (mime.indexOf("text/xml") == 0
|| mime.indexOf("application/xml") == 0) {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
Element eBody = doc.createElementNS(CTL_NS, "ctl:body");
// Fortify Mod: prevent external entity injection
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer t = tf.newTransformer();
// Transformer t = TransformerFactory.newInstance().newTransformer();
// End Fortify Mod
t.transform(new StreamSource(bais), new DOMResult(eBody));
eRequest.appendChild(eBody);
} else if (mime.indexOf("text/") == 0) {
Element eBody = doc.createElementNS(CTL_NS, "ctl:body");
eBody.appendChild(doc.createCDATASection(data.toString()));
eRequest.appendChild(eBody);
}
}
return eRequest;
}
static void copy_stream(InputStream in, OutputStream out)
throws IOException {
int i = in.read();
while (i >= 0) {
out.write(i);
i = in.read();
}
}
public static String getBaseServletURL() {
return baseServletURL;
}
public static void setBaseServletURL(String baseServletURL) {
MonitorServlet.baseServletURL = baseServletURL;
}
}