//////////////////////////////////////////////////////////////////////// // // Copyright (c) 2009-2013 Denim Group, Ltd. // // The contents of this file are subject to the Mozilla Public License // Version 2.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://www.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is ThreadFix. // // The Initial Developer of the Original Code is Denim Group, Ltd. // Portions created by Denim Group, Ltd. are Copyright (C) // Denim Group, Ltd. All Rights Reserved. // // Contributor(s): Denim Group, Ltd. // //////////////////////////////////////////////////////////////////////// package com.denimgroup.threadfix.service.channel; import java.util.Calendar; import java.util.LinkedList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.denimgroup.threadfix.data.dao.ChannelSeverityDao; import com.denimgroup.threadfix.data.dao.ChannelTypeDao; import com.denimgroup.threadfix.data.dao.ChannelVulnerabilityDao; import com.denimgroup.threadfix.data.entities.ChannelType; import com.denimgroup.threadfix.data.entities.DataFlowElement; import com.denimgroup.threadfix.data.entities.Finding; import com.denimgroup.threadfix.data.entities.Scan; import com.denimgroup.threadfix.webapp.controller.ScanCheckResultBean; /** * * @author mcollins */ public class FindBugsChannelImporter extends AbstractChannelImporter { @Autowired public FindBugsChannelImporter(ChannelTypeDao channelTypeDao, ChannelVulnerabilityDao channelVulnerabilityDao, ChannelSeverityDao channelSeverityDao) { this.channelTypeDao = channelTypeDao; this.channelVulnerabilityDao = channelVulnerabilityDao; this.channelSeverityDao = channelSeverityDao; this.channelType = channelTypeDao.retrieveByName(ChannelType.FINDBUGS); } @Override public Scan parseInput() { return parseSAXInput(new FindBugsSAXParser()); } private Calendar getCalendarFromTimeInMillisString(String timeInMillis) { try { Long timeLong = Long.valueOf(timeInMillis); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timeLong); return calendar; } catch (NumberFormatException e) { log.warn("Invalid date timestamp in FindBugs file.", e); return null; } } public class FindBugsSAXParser extends DefaultHandler { private Boolean inSecurityBug = false; private Boolean getDataFlowElements = false; private String currentChannelVulnCode = null; private String currentPath = null; private String currentParameter = null; private String currentSeverityCode = null; private List<DataFlowElement> dataFlowElements = null; private int dataFlowPosition; public void add(Finding finding) { if (finding != null) { finding.setNativeId(getNativeId(finding)); finding.setIsStatic(true); saxFindingList.add(finding); } } public DataFlowElement getDataFlowElement(Attributes atts, int position) { String start = atts.getValue("start"); Integer lineNum = null; if (start != null) { try { lineNum = Integer.valueOf(start); } catch (NumberFormatException e) { log.error("FindBugs had a non-integer value in its line number field.", e); } } if (lineNum == null) { lineNum = -1; } return new DataFlowElement(null, lineNum, atts.getValue("sourcefile"), position); } //////////////////////////////////////////////////////////////////// // Event handlers. //////////////////////////////////////////////////////////////////// public void startElement (String uri, String name, String qName, Attributes atts) { if ("BugCollection".equals(qName)) { String timeString = atts.getValue("timestamp"); if (timeString != null) { date = getCalendarFromTimeInMillisString(timeString); } } else if ("BugInstance".equals(qName) && "SECURITY".equals(atts.getValue("category"))) { inSecurityBug = true; currentChannelVulnCode = atts.getValue("type"); currentSeverityCode = atts.getValue("priority"); } else if (inSecurityBug && "LocalVariable".equals(qName)) { currentParameter = atts.getValue("name"); } else if (inSecurityBug && "SourceLine".equals(qName)) { if (currentPath == null) { currentPath = atts.getValue("sourcepath"); } if (getDataFlowElements) { if (dataFlowElements != null) { dataFlowElements.add(getDataFlowElement(atts,dataFlowPosition++)); } } if ("SOURCE_LINE_GENERATED_AT".equals(atts.getValue("role"))) { getDataFlowElements = true; dataFlowElements = new LinkedList<DataFlowElement>(); dataFlowElements.add(getDataFlowElement(atts,0)); dataFlowPosition = 1; } } } public void endElement (String uri, String name, String qName) { if (inSecurityBug && "BugInstance".equals(qName)) { Finding finding = constructFinding(currentPath, currentParameter, currentChannelVulnCode, currentSeverityCode); finding.setDataFlowElements(dataFlowElements); add(finding); inSecurityBug = false; currentPath = null; currentParameter = null; currentChannelVulnCode = null; currentSeverityCode = null; dataFlowElements = null; dataFlowPosition = 0; getDataFlowElements = false; } } } @Override public ScanCheckResultBean checkFile() { return testSAXInput(new FindBugsSAXValidator()); } public class FindBugsSAXValidator extends DefaultHandler { private boolean hasFindings = false; private boolean hasDate = false; private boolean correctFormat = false; private void setTestStatus() { if (!correctFormat) testStatus = ScanImportStatus.WRONG_FORMAT_ERROR; else if (hasDate) testStatus = checkTestDate(); if ((testStatus == null || ScanImportStatus.SUCCESSFUL_SCAN == testStatus) && !hasFindings) testStatus = ScanImportStatus.EMPTY_SCAN_ERROR; else if (testStatus == null) testStatus = ScanImportStatus.SUCCESSFUL_SCAN; } private ScanImportStatus checkTestDate() { if (applicationChannel == null || testDate == null) return ScanImportStatus.OTHER_ERROR; List<Scan> scanList = applicationChannel.getScanList(); for (Scan scan : scanList) { if (scan != null && scan.getImportTime() != null) { int result = scan.getImportTime().compareTo(testDate); if (result == 0) { return ScanImportStatus.DUPLICATE_ERROR; } else if (result > 0) { return ScanImportStatus.OLD_SCAN_ERROR; } else if (scan.getImportTime().getTimeInMillis() % 1000 == 0 && (scan.getImportTime().getTimeInMillis() / 1000) == (testDate.getTimeInMillis() / 1000)){ // MySQL doesn't support milliseconds. FindBugs does. // This should make it work. return ScanImportStatus.DUPLICATE_ERROR; } } } log.info("Scan time compare returning success."); return ScanImportStatus.SUCCESSFUL_SCAN; } //////////////////////////////////////////////////////////////////// // Event handlers. //////////////////////////////////////////////////////////////////// public void endDocument() { setTestStatus(); } public void startElement (String uri, String name, String qName, Attributes atts) throws SAXException { if ("BugCollection".equals(qName)) { String timeString = atts.getValue("timestamp"); if (timeString != null) { testDate = getCalendarFromTimeInMillisString(timeString); } hasDate = testDate != null; correctFormat = true; } if ("BugInstance".equals(qName) && "SECURITY".equals(atts.getValue("category"))) { hasFindings = true; setTestStatus(); throw new SAXException(FILE_CHECK_COMPLETED); } } } }