/** * ************************************************************************** * * Contributor(s): * C. Heazel (WiSC): Added Fortify adjudication changes * *************************************************************************** */ package com.occamlab.te.web; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; /** * Monitors which service capabilities are exercised by a client request. A * representation of a service implementation conformance statement (ICS) is * updated according to the content of a request message; that is, any supplied * parameter options are pruned from the initial tree. * <p> * When the test session is terminated the residual tree includes only those * request parameter values that did <strong>not</strong> appear in any request, * thus indicating which implemented options were not covered. * </p> * * <p> * A sample representation of an ICS for the GetCapabilities request is shown in * the listing below. * </p> * * <pre> * {@code * <request name="GetCapabilities"> * <param name="format"> * <value>text/xml</value> * </param> * <param name="updatesequence"> * <value>0</value> * </param> * </request> * } * </pre> * */ public class CoverageMonitor { private static final Logger LOGR = Logger.getLogger(CoverageMonitor.class .getPackage().getName()); private static final Map<URI, String> ICS_MAP = createICSMap(); private static Map<URI, String> createICSMap() { HashMap<URI, String> icsMap = new HashMap<URI, String>(); icsMap.put(URI.create("urn:wms_client_test_suite/GetCapabilities"), "WMS-GetCapabilities.xml"); icsMap.put(URI.create("urn:wms_client_test_suite/GetMap"), "WMS-GetMap.xml"); icsMap.put(URI.create("urn:wms_client_test_suite/GetFeatureInfo"), "WMS-GetFeatureInfo.xml"); return icsMap; } private URI requestId; private Document coverageDoc; private File testSessionDir; /** * Creates a CoverageMonitor for a given service request. * * @param uri * A URI value that identifies a service request message. */ public CoverageMonitor(String uri) { this.requestId = URI.create(uri); String icsPath = "/coverage/" + ICS_MAP.get(this.requestId); try { // Fortify Mod: Disable entity expansion to foil External Entity Injections DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); df.setNamespaceAware(true); df.setExpandEntityReferences(false); DocumentBuilder docBuilder = df.newDocumentBuilder(); // DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); // End Fortify Mods this.coverageDoc = docBuilder.parse( getClass().getResourceAsStream(icsPath), null); } catch (Exception e) { LOGR.warning(e.getMessage()); } LOGR.config("Created coverage monitor using ICS at " + icsPath); } /** * Returns the location of the test session directory. * * @return A File object denoting a directory in the local file system. */ public File getTestSessionDir() { return testSessionDir; } /** * Sets the location of the test session directory where the coverage * results will be written to. * * @param sessionDir * A File object (it should correspond to a directory). */ public void setTestSessionDir(File sessionDir) { if (!sessionDir.isDirectory()) { throw new IllegalArgumentException("Not a directory: " + testSessionDir); } this.testSessionDir = sessionDir; } /** * Inspects the query part of a GET request URI and updates the ICS document * by removing parameter values that occur in the request. The initial * coverage report is modified over the course of the test run as requests * are received; the residual document includes only those parameter values * that were <em>not requested</em>. * * @param query * The (decoded) query component of a GET request. */ void inspectQuery(String query) { Map<String, String> qryParams = new HashMap<String, String>(); for (String param : query.split("&")) { String[] nvp = param.split("="); if(nvp.length > 1){ qryParams.put(nvp[0].toLowerCase(), nvp[1]); }else{ qryParams.put(nvp[0].toLowerCase(), ""); } } String reqType = qryParams.get("request"); XPath xpath = XPathFactory.newInstance().newXPath(); for (Map.Entry<String, String> paramEntry : qryParams.entrySet()) { String[] paramValues = paramEntry.getValue().split(","); for (int i = 0; i < paramValues.length; i++) { String expr = String .format("//request[@name='%s']/param[@name='%s']/value[text() = '%s']", reqType, paramEntry.getKey(), paramValues[i]); NodeList result = null; try { result = (NodeList) xpath.evaluate(expr, this.coverageDoc, XPathConstants.NODESET); } catch (XPathExpressionException xpe) { LOGR.log(Level.WARNING, "Failed to evaluate expression " + expr, xpe); } if ((null != result) && (result.getLength() > 0)) { for (int j = 0; j < result.getLength(); j++) { Node value = result.item(j); Element param = (Element) value.getParentNode(); param.removeChild(value); // remove empty param element if (param.getElementsByTagName("value").getLength() == 0) { Node request = param.getParentNode(); request.removeChild(param); } } } } } } /** * Writes the residual ICS document to a file in the test session directory. */ public void writeCoverageResults() { File coverageFile = new File(this.testSessionDir, ICS_MAP.get(this.requestId)); if (coverageFile.exists()) { return; } OutputStream fos = null; try { fos = new FileOutputStream(coverageFile, false); writeDocument(fos, this.coverageDoc); } catch (FileNotFoundException e) { } finally { try { fos.close(); LOGR.config("Wrote coverage results to " + coverageFile.getCanonicalPath()); } catch (IOException ioe) { LOGR.warning(ioe.getMessage()); } } } /** * Writes a DOM Document to the given OutputStream using the "UTF-8" * encoding. The XML declaration is omitted. * * @param outStream * The destination OutputStream object. * @param doc * A Document node. */ void writeDocument(OutputStream outStream, Document doc) { DOMImplementationRegistry domRegistry = null; try { domRegistry = DOMImplementationRegistry.newInstance(); } catch (Exception e) { LOGR.warning(e.getMessage()); } DOMImplementationLS impl = (DOMImplementationLS) domRegistry .getDOMImplementation("LS"); LSSerializer writer = impl.createLSSerializer(); writer.getDomConfig().setParameter("xml-declaration", false); writer.getDomConfig().setParameter("format-pretty-print", true); LSOutput output = impl.createLSOutput(); output.setEncoding("UTF-8"); output.setByteStream(outStream); writer.write(doc, output); } }