gov.nist.appvet.tool.sigverifier.Service.java Source code

Java tutorial

Introduction

Here is the source code for gov.nist.appvet.tool.sigverifier.Service.java

Source

/* This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 United States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 * 
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS".  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof including, but
 * not limited to, the correctness, accuracy, reliability or usefulness of
 * the software.
 * 
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement.
 */
package gov.nist.appvet.tool.sigverifier;

import gov.nist.appvet.tool.sigverifier.util.FileUtil;
import gov.nist.appvet.tool.sigverifier.util.HttpUtil;
import gov.nist.appvet.tool.sigverifier.util.Logger;
import gov.nist.appvet.tool.sigverifier.util.Protocol;
import gov.nist.appvet.tool.sigverifier.util.ReportFormat;
import gov.nist.appvet.tool.sigverifier.util.ReportUtil;
import gov.nist.appvet.tool.sigverifier.util.ToolStatus;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * This class implements a synchronous tool service.
 */
public class Service extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final String reportName = "report";
    private static final Logger log = Properties.log;
    private static String appDirPath = null;
    private String appFilePath = null;
    private String reportFilePath = null;
    private String fileName = null;
    private String appId = null;
    private String command = null;
    private StringBuffer reportBuffer = null;

    /** CHANGE (START): Add expected HTTP request parameters **/
    /** CHANGE (END): Add expected HTTP request parameters **/
    public Service() {
        super();
    }

    /*
     * // AppVet tool services will rarely use HTTP GET protected void
     * doGet(HttpServletRequest request, HttpServletResponse response) throws
     * ServletException, IOException {
     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Get received HTTP parameters and file upload
        FileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        List items = null;
        FileItem fileItem = null;

        try {
            items = upload.parseRequest(request);
        } catch (FileUploadException e) {
            e.printStackTrace();
        }

        // Get received items
        Iterator iter = items.iterator();
        FileItem item = null;

        while (iter.hasNext()) {
            item = (FileItem) iter.next();
            if (item.isFormField()) {
                // Get HTML form parameters
                String incomingParameter = item.getFieldName();
                String incomingValue = item.getString();
                if (incomingParameter.equals("appid")) {
                    appId = incomingValue;
                }
                /** CHANGE (START): Get other tools-specific form parameters **/
                /** CHANGE (END): Get other tools-specific form parameters **/
            } else {
                // item should now hold the received file
                if (item != null) {
                    fileItem = item;
                    log.debug("Received file: " + fileItem.getName());
                }
            }
        }

        if (appId == null) {
            // All tool services require an AppVet app ID
            HttpUtil.sendHttp400(response, "No app ID specified");
            return;
        }

        if (fileItem != null) {
            // Get app file
            fileName = FileUtil.getFileName(fileItem.getName());
            if (!fileName.endsWith(".apk")) {
                HttpUtil.sendHttp400(response, "Invalid app file: " + fileItem.getName());
                return;
            }
            // Create app directory
            appDirPath = Properties.TEMP_DIR + "/" + appId;
            File appDir = new File(appDirPath);
            if (!appDir.exists()) {
                appDir.mkdir();
            }
            // Create report path
            reportFilePath = Properties.TEMP_DIR + "/" + appId + "/" + reportName + "."
                    + Properties.reportFormat.toLowerCase();

            appFilePath = Properties.TEMP_DIR + "/" + appId + "/" + fileName;
            log.debug("App file path: " + appFilePath);
            if (!FileUtil.saveFileUpload(fileItem, appFilePath)) {
                HttpUtil.sendHttp500(response, "Could not save uploaded file");
                return;
            }
        } else {
            HttpUtil.sendHttp400(response, "No app was received.");
            return;
        }

        // Use if reading command from ToolProperties.xml. Otherwise,
        // comment-out if using custom command (called by customExecute())
        command = getCommand();

        // If asynchronous, send acknowledgement back to AppVet now
        if (Properties.protocol.equals(Protocol.ASYNCHRONOUS.name())) {
            HttpUtil.sendHttp202(response, "Received app " + appId + " for processing.");
        }
        /*
         * CHANGE: Select either execute() to execute a native OS command or
         * customExecute() to execute your own custom code. Make sure that the
         * unused method call is commented-out.
         */
        reportBuffer = new StringBuffer();
        boolean succeeded = execute(command, reportBuffer);

        // Delay for demo purposes
        try {
            Thread.sleep(Properties.delay);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // boolean succeeded = customExecute(reportBuffer);
        if (!succeeded) {
            log.error("Error detected: " + reportBuffer.toString());
            String errorReport = ReportUtil.getHtmlReport(response, fileName, ToolStatus.ERROR,
                    reportBuffer.toString(), "Description: \tApp is signed.\n\n",
                    "Description: \tApp is unsigned or incorrectly signed.\n\n", null,
                    "Description: \tError or exception processing app.\n\n");
            // Send report to AppVet
            if (Properties.protocol.equals(Protocol.SYNCHRONOUS.name())) {
                // Send back ASCII in HTTP Response
                ReportUtil.sendInHttpResponse(response, errorReport, ToolStatus.ERROR);
            } else if (Properties.protocol.equals(Protocol.ASYNCHRONOUS.name())) {
                // Send report file in new HTTP Request to AppVet
                if (FileUtil.saveReport(errorReport, reportFilePath)) {
                    ReportUtil.sendInNewHttpRequest(appId, reportFilePath, ToolStatus.ERROR);
                }
            }
            return;
        }

        // Analyze report and generate tool status
        log.debug("Analyzing report for " + appFilePath);
        ToolStatus reportStatus = analyzeReport(reportBuffer.toString());
        log.debug("Result: " + reportStatus.name());
        String reportContent = null;

        // Get report
        if (Properties.reportFormat.equals(ReportFormat.HTML.name())) {
            reportContent = ReportUtil.getHtmlReport(response, fileName, reportStatus, reportBuffer.toString(),
                    "Description: \tApp is signed (Note: some warnings may exist. See below for details).\n\n",
                    "Description: \tApp is unsigned or incorrectly signed.\n\n",
                    "Description: \tApp is unsigned or incorrectly signed.\n\n",
                    "Description: \tError or exception processing app.\n\n");
        } else if (Properties.reportFormat.equals(ReportFormat.TXT.name())) {
            reportContent = getTxtReport();
        } else if (Properties.reportFormat.equals(ReportFormat.PDF.name())) {
            reportContent = getPdfReport();
        } else if (Properties.reportFormat.equals(ReportFormat.JSON.name())) {
            reportContent = getJsonReport();
        }

        // If report is null or empty, stop processing
        if (reportContent == null || reportContent.isEmpty()) {
            log.error("Tool report is null or empty");
            return;
        }

        // Send report to AppVet
        if (Properties.protocol.equals(Protocol.SYNCHRONOUS.name())) {
            // Send back ASCII in HTTP Response
            ReportUtil.sendInHttpResponse(response, reportContent, reportStatus);
        } else if (Properties.protocol.equals(Protocol.ASYNCHRONOUS.name())) {
            // Send report file in new HTTP Request to AppVet
            if (FileUtil.saveReport(reportContent, reportFilePath)) {
                ReportUtil.sendInNewHttpRequest(appId, reportFilePath, reportStatus);
            }
        }

        // Clean up
        if (!Properties.keepApps) {
            if (FileUtil.deleteDirectory(new File(appDirPath))) {
                log.debug("Deleted " + appFilePath);
            } else {
                log.warn("Could not delete " + appFilePath);
            }
        }

        reportBuffer = null;

        // Clean up
        System.gc();
    }

    public static ToolStatus analyzeReport(String report) {
        if (report == null || report.isEmpty()) {
            log.error("Report is null or empty.");
            return ToolStatus.ERROR;
        }
        // Scan file for result strings defined in configuration file. Here,
        // we always scan in this order: ERRORs, HIGHs, MODERATEs, and LOWs.
        if (Properties.errorResults != null && !Properties.errorResults.isEmpty()) {
            for (String s : Properties.errorResults) {
                if (report.indexOf(s) > -1) {
                    log.debug("Error message: " + s);
                    return ToolStatus.ERROR;
                }
            }
        }
        if (Properties.highResults != null && !Properties.highResults.isEmpty()) {
            for (String s : Properties.highResults) {
                if (report.indexOf(s) > -1) {
                    log.debug("High message: " + s);
                    return ToolStatus.HIGH;
                }
            }
        }
        if (Properties.moderateResults != null && !Properties.moderateResults.isEmpty()) {
            for (String s : Properties.moderateResults) {
                if (report.indexOf(s) > -1) {
                    log.debug("Moderate message: " + s);
                    return ToolStatus.MODERATE;
                }
            }
        }
        if (Properties.lowResults != null && !Properties.lowResults.isEmpty()) {
            for (String s : Properties.lowResults) {
                if (report.indexOf(s) > -1) {
                    log.debug("Low message: " + s);
                    return ToolStatus.LOW;
                }
            }
        }
        return Properties.defaultStatus;
    }

    public String getCommand() {
        // Get command from ToolProperties.xml file
        String cmd1 = Properties.command;
        String cmd2 = null;
        if (cmd1.indexOf(Properties.APP_FILE_PATH) > -1) {
            // Add app file path
            cmd2 = cmd1.replace(Properties.APP_FILE_PATH, appFilePath);
        } else {
            cmd2 = cmd1;
        }

        log.debug("full command: " + cmd2);
        return cmd2;
    }

    private static boolean execute(String command, StringBuffer output) {
        List<String> commandArgs = Arrays.asList(command.split("\\s+"));
        ProcessBuilder pb = new ProcessBuilder(commandArgs);
        Process process = null;
        IOThreadHandler outputHandler = null;
        IOThreadHandler errorHandler = null;
        int exitValue = -1;
        try {
            if (command == null || command.isEmpty()) {
                log.error("Command is null or empty");
                return false;
            }
            log.debug("Executing " + command);
            process = pb.start();
            outputHandler = new IOThreadHandler(process.getInputStream());
            outputHandler.start();
            errorHandler = new IOThreadHandler(process.getErrorStream());
            errorHandler.start();
            if (process.waitFor(Properties.commandTimeout, TimeUnit.MILLISECONDS)) {
                // Process has waited and exited within the timeout
                exitValue = process.exitValue();
                if (exitValue == 0) {
                    log.debug("Command terminated normally: \n" + outputHandler.getOutput() + "\nErrors: "
                            + errorHandler.getOutput());
                    StringBuffer resultOut = outputHandler.getOutput();
                    output.append(resultOut);
                    return true;
                } else {
                    log.error("Command terminated abnormally: \n" + outputHandler.getOutput() + "\nErrors: "
                            + errorHandler.getOutput());
                    StringBuffer resultError = errorHandler.getOutput();
                    output.append(resultError);
                    return false;
                }
            } else {
                // Process exceed timeout or was interrupted
                log.error("Command timed-out or was interrupted: \n" + outputHandler.getOutput() + "\nErrors: "
                        + errorHandler.getOutput());
                StringBuffer resultOutput = outputHandler.getOutput();
                StringBuffer resultError = errorHandler.getOutput();
                if (resultOutput != null) {
                    output.append(resultOutput);
                    return false;
                } else if (resultError != null) {
                    output.append(resultError);
                } else {
                    output.append(Properties.toolName + " timed-out");
                }
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        } finally {
            if (outputHandler.isAlive()) {
                try {
                    outputHandler.inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (errorHandler.isAlive()) {
                try {
                    errorHandler.inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (process.isAlive()) {
                process.destroy();
            }
        }
    }

    private static class IOThreadHandler extends Thread {
        private InputStream inputStream;
        private StringBuffer output = new StringBuffer();
        private static final String lineSeparator = System.getProperty("line.separator");;

        IOThreadHandler(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        public void run() {
            Scanner br = null;

            br = new Scanner(new InputStreamReader(inputStream));
            String line = null;
            while (br.hasNextLine()) {
                line = br.nextLine();
                output.append(line + lineSeparator);
            }
            br.close();
        }

        public StringBuffer getOutput() {
            return output;
        }
    }

    // TODO
    public String getTxtReport() {
        return null;
    }

    // TODO
    public String getPdfReport() {
        return null;
    }

    // TODO
    public String getJsonReport() {
        return null;
    }
}