/*
 * Licensed Materials - Property of IBM* Corp. and/or HCL**
 * UrbanCode Deploy
 * (c) Copyright IBM Corporation 2016, 2017 All Rights Reserved.
 * (c) Copyright HCL Corporation 2018, 2019 All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.

 * * Trademark of International Business Machines
 * ** Trademark of HCL Technologies Limited
 */

package com.urbancode.air.plugin.appscan

import java.io.StringWriter
import java.io.File
import java.io.FileInputStream
import java.net.URL
import java.net.URLEncoder
import java.text.Format
import java.text.SimpleDateFormat
import java.util.Iterator

import javax.xml.XMLConstants
import javax.xml.namespace.NamespaceContext
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

import org.apache.commons.io.FileUtils
import org.apache.http.client.methods.HttpDelete
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpPut
import org.apache.http.client.methods.HttpUriRequest
import org.apache.http.entity.InputStreamEntity
import org.apache.http.entity.StringEntity
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.util.EntityUtils
import org.w3c.dom.Document

import com.urbancode.air.XTrustProvider
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

import groovy.json.JsonSlurper
import groovy.json.JsonParserType


import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.entity.mime.content.StringBody
import org.apache.http.entity.mime.content.FileBody

public class AppScanRestHelper {

    def user
    def password
    def baseUrl
    def disableCerts
    def client
    String sessionId
    XPath xpath

    // Retrieve Reports parameters
    def reportsFIID
    def filepath
    def reportName
    Boolean printReport

    // Run Scan parameters
    def rerun
    def scanFIID
    int timeout
    Boolean waitForScanComp
    Boolean stopScan

    // Create Scan parameters
    def appID
    def testPolicyId
    def folderID
    def scanDescription
    def scanName
    def autoScanName
    def templateName
    def jobID

    // Configure Job Options parameters
    def httpAuth
    def httpPassword
    def httpUser
    def recordedTraffic
    def loginFile
    def setAuto
    def scanLimit
    def scanPassword
    def startUrl
    def scanUser
    def scanType
    def loginMethod
    // Retrieve PDF Report parameters
    def port
    def applicationID
    def pdfReportFileType

    // Webhook Paylod parameters
    def payload

    // Folder Item parameter (used to delete)
    def folderItem

    public AppScanRestHelper(props) {
        this.appID = props['appID']?.trim()
        this.testPolicyId = props['testPolicyId']?.trim()
        this.applicationID = props['applicationID']?.trim()
        this.baseUrl = props['baseUrl']?.trim()
        this.disableCerts = Boolean.valueOf(props['disableCerts'])
        this.filepath = props['filepath']?.trim()
        this.folderID = props['folderID']?.trim()
        this.httpAuth = props['httpAuth']?.trim()
        this.httpUser = props['httpUser']?.trim()
        this.httpPassword = props['httpPassword']
        this.scanLimit = props['scanLimit']?.trim()
        this.password = props['password']
        this.port = props['port']?.trim()
        this.printReport = Boolean.valueOf(props['printReport'])
        this.recordedTraffic = props['recordedTraffic']?.trim()
        this.loginFile = props['loginFile']?.trim()
        this.reportsFIID = props['reportsFIID']?.trim()
        this.reportName = props['reportName']?.trim()
        this.pdfReportFileType = props['pdfReportFileType']?.trim()
        if (props['rerun']) {
            this.rerun = (props['rerun'] as int)
        }
        this.loginMethod = props['loginMethod']?.trim()
        this.scanType = props['scanType']?.trim()
        this.scanDescription = props['scanDescription']?.trim()
        this.scanFIID = props['scanFIID']?.trim()
        this.scanName = props['scanName']?.trim()
        this.autoScanName = props['autoScanName']?.trim()
        this.scanPassword = props['scanPassword']
        this.scanUser = props['scanUser']?.trim()
        this.setAuto = props['setAuto']
        this.startUrl = props['startUrl']?.trim()
        this.templateName = props['templateName']?.trim()
        this.waitForScanComp = Boolean.valueOf(props['waitForScanComp'])
        this.stopScan = Boolean.valueOf(props['stopScan'])
        if (props['timeout']) {
            this.timeout = props['timeout']
        }
        this.user = props['user']?.trim()

        // Gather AppScan Webhook Payload
        this.payload = props['payload']?.trim()

        // Folder Item for deletion
        this.folderItem = props['folderItem']?.trim()

        // baseUrl syntax differs between steps
         if (!baseUrl.endsWith("/")) {
            baseUrl += "/"
        }

        if ((filepath) && (!filepath.endsWith(File.separator))) {
            filepath += File.separator
        }

        HttpClientBuilder builder = new HttpClientBuilder()

        if (disableCerts) {
            XTrustProvider.install()
            builder.setTrustAllCerts(true)
        }

        builder.setUsername(user)
        builder.setPassword(password)
        builder.setPreemptiveAuthentication(true)

        this.client = builder.buildClient()

        System.setProperty("https.protocols", "TLSv1")

        // Initialization of XPath utilities
        XPathFactory xfactory = XPathFactory.newInstance()
        xpath = xfactory.newXPath()
        xpath.setNamespaceContext(_nsContext)

        try {
            this.sessionId = authenticate()
            println "Logged in."
        } catch (Exception e) {
            println "[Error] Failed to authenticate."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public def retrieveReportInfo() {
        def outProps = []
        int stateInt = ScanState.UNKNOWN
        int runResult = 1
        try {
            // Confirm Report is ready to be retrieved
            stateInt = waitForReady(reportsFIID)
            if (stateInt == ScanState.READY) {
                outProps = getReportInfo(reportName)
                println "Retrieved Report successfully."
                runResult = 0
            } else {
                println "[Error] Unable to retrieve report."
                runResult = 1
            }
        } catch (Exception e) {
            println "[Error] Failed report retrieval."
            e.printStackTrace()
            runResult = 1
        }
        outProps = [getKey(stateInt), runResult] + outProps
        return outProps
    }

     public def retrieveReportInfoV2() {
        try {
        long startTime = System.currentTimeMillis()
        String checkReportStatus
        Thread.sleep(100000)
        while (System.currentTimeMillis() < startTime + 1000000) {

            HttpGet folderRequestCheck = new HttpGet(baseUrl + "ase/api/folderitems/"+reportsFIID+"/reports")
            def responseCheck = doRequestV2(folderRequestCheck,null)
            HttpEntity res_entity = responseCheck.getEntity()
            def statusLine = responseCheck.getStatusLine()
            def statusCode = statusLine.getStatusCode()
            def reportResult = EntityUtils.toString(res_entity)
            if(statusCode == 200) {
                if(reportResult.indexOf("The specified report pack was not found") > -1) {
                    println "[Error] The specified report pack was not found."
                    System.exit(1)  
                } else if(reportResult.indexOf("This report pack has not yet been run") > -1) {
                    println "Waiting for the report to be generated ..."
                } else {
                break
                }
            } else {
                println ("[Error Retrieving report failed: " + reportResult)
                println (" Status code: " + statusCode)
                System.exit(1)
            }
            Thread.sleep(10000)
        }

            HttpGet folderRequest = new HttpGet(baseUrl + "ase/api/folderitems/"+reportsFIID+"/reports")
            def response = doRequestV2(folderRequest,null)
            HttpEntity r_entity = response.getEntity()
            def folderResult = EntityUtils.toString(r_entity)
            if(folderResult.indexOf("last-updated") > -1) {
            def jsonSlurper = new JsonSlurper()
            def responseObject = jsonSlurper.parseText(folderResult)
            if(responseObject.reports.report) {
            def reportList = responseObject.reports.report
            Integer lowCount = 0
            Integer mediumCount = 0
            Integer highCount = 0
            Integer criticalCount = 0
            Boolean foundItem = false

            for(def item: reportList) {
                if(item["issue-counts-severity"]) {
                    if(reportName) {
                        if(item.name == reportName) {
                            Integer arraySizeReport = item["issue-counts-severity"]["issue-count"].size()
                            if(arraySizeReport > 0) {
                            def severityReportArray =  item["issue-counts-severity"]["issue-count"]
                            foundItem = true
                                for(def severityItem: severityReportArray) {
                                    if(severityItem.severity.name == "Low") {
                                        lowCount = lowCount + severityItem.count
                                    } 
                                    if(severityItem.severity.name == "Medium") {
                                        mediumCount = mediumCount + severityItem.count
                                    } 
                                    if(severityItem.severity.name == "High") {
                                        highCount = highCount + severityItem.count
                                    } 
                                    if(severityItem.severity.name == "Critical") {
                                        criticalCount = criticalCount + severityItem.count
                                    } 
                                    }
                                    }
                        }
                    } else {
                    Integer arraySize = item["issue-counts-severity"]["issue-count"].size()
                    if(arraySize > 0) {
                    def severityArray =  item["issue-counts-severity"]["issue-count"]
                        for(def severityItem: severityArray) {
                            if(severityItem.severity.name == "Low") {
                                lowCount = lowCount + severityItem.count
                            } 
                            if(severityItem.severity.name == "Medium") {
                                mediumCount = mediumCount + severityItem.count
                            } 
                            if(severityItem.severity.name == "High") {
                                highCount = highCount + severityItem.count
                            } 
                            if(severityItem.severity.name == "Critical") {
                                criticalCount = criticalCount + severityItem.count
                            } 
                            }
                    }
                    }

                }
            }

            if(reportName && !foundItem) {
                println "[Error] Report name not found."
                System.exit(1)  
            }
            if(reportName && foundItem) {
                try {
                    println "[Success] Retrieving data of "+reportName + "........"
                    String reportPackURL = "ase/api/folderitems/" + reportsFIID +"/reports?folderItemReportsInEnglish=false"
                    HttpResponse jsResp = sendGetRequestV2(baseUrl + reportPackURL)
                    HttpEntity r_entityJs = jsResp.getEntity()
                    def ent = EntityUtils.toString(r_entityJs)

                    String fileSummaryName = reportsFIID + "-Summary.json"
                    File reportSummaryFile = new File(filepath + fileSummaryName)

                    try {
                        def fileWriter = new FileWriter(filepath + fileSummaryName)
                        fileWriter.write(ent)
                        fileWriter.close()
                        println "[Ok] Generated a Specific Report JSON: ${reportSummaryFile.getCanonicalPath()}"
                    } 
                    catch (IOException e) {
                        println "Error occurred while writing to the file: ${e.message}"
                    }
                } 
                catch (IOException e) {
                    println "Error occurred while writing to the file: ${e.message}"
                }
            }
            println "Low Severity Issues: "+lowCount
            println "Medium Severity Issues: "+mediumCount
            println "High Severity Issues: "+highCount
            println "Critical Severity Issues: "+criticalCount
            println "Retrieved Report successfully."

            } else {
            println "[Error] Failed to retrieve reports data."
            System.exit(1) 
            }
            } else {
            println folderResult
            System.exit(1) 
            }

        } catch (Exception e) {
            println "[Error] Failed report retrieval."
            e.printStackTrace()
        }
    }

    public String[] retrievePDFReportInfo() {
        Thread.sleep(100000)
        String[] outProps = []
        try {
            outProps = getPDFReportInfo()
            println "Retrieved PDF Report successfully."
        } catch (Exception e) {
            println "[Error] Failed report retrieval."
            e.printStackTrace()
            System.exit(1)
        }
        return outProps
    }

    public def runScan() {
        int status = ScanState.UNKNOWN
        def result = [status, 1]
        def runNumber = 1
        def retry = true
        if (rerun) {
            runNumber += rerun
        }

        while (retry) {
            try {
                status = runScanByFIID()
                // 0 is success
                // 1 is failure
                if (!waitForScanComp && (status == ScanState.READY || status == ScanState.RUNNING)) {
                    // Success if Wait for Scan Completion is unchecked
                    println "Scan in a RUNNING or READY state."
                    result = [getKey(status), 0]
                } else if (status == ScanState.READY){
                    println "Scan and/or Report Pack finished successfully."
                    // Success if Wait for Scan Completion is checked
                    result = [getKey(status), 0]
                } else {
                    // Fail if a non-running/ready state is found
                    result = [getKey(status), 1]
                }
            } catch (Exception e) {
                println "[Error] Failed scan."
                e.printStackTrace()
            } finally {
                // If failure found, retry
                if (result[1] == 1 && runNumber > 1) {
                    status = stopScanByFIID()
                    println ""
                    println "Rerunning scan."
                    runNumber -= 1
                }
                else {
                    if (result[1] == 1 && stopScan) {
                        status = stopScanByFIID()
                        result = [getKey(status), 1]
                    }
                    retry = false
                }
            }
        }

        return result
    }

    public void listTemplates() {
        Document doc = sendGetRequest(baseUrl + "ase/services/templates")
        printOuterXml(doc)
        println ""
    }

        public void listTemplatesV2() {
            HttpGet templateRequest = new HttpGet(baseUrl + "ase/api/templates")
            def response = doRequestV2(templateRequest,null)
            HttpEntity r_entity = response.getEntity()
            def templateResult = EntityUtils.toString(r_entity)
            def jsonSlurper = new JsonSlurper()
            def responseObject = jsonSlurper.parseText(templateResult)
            for(def item: responseObject) {
                println " ID - ${item.id} || Folder ID - ${item.folderId} || Name - ${item.name}"
        }

    }

    public String[] createScan() {
        try {
            String[] outProps = createScanByTemplateID()
            println "Scan created successfully."
            return outProps
        } catch (Exception e) {
            println "[Error] Failed scan creation."
            e.printStackTrace()
            System.exit(1)
        }
    }

    private void deleteFolderItem() {
        String folderItemURL = baseUrl + "ase/services/folderitems/" + folderItem

        try {
            sendDeleteRequest(folderItemURL)
        } catch (Exception e) {
            println "[Error] Failed folder item deletion."
            e.printStackTrace()
            System.exit(1)
        }
        println("[Info] Successfully deleted folder item `${folderItem}`.")
    }

    private void deleteFolderItemV2() {
        try {
            HttpDelete deleteRequest = new HttpDelete(baseUrl + "ase/api/folderitems/" + folderItem)

            def response = doRequestV2(deleteRequest,null)
            def statusLine = response.getStatusLine()
            def statusCode = statusLine.getStatusCode()

            if(statusCode == 200) {
             println("[Info] Successfully deleted folder item `${folderItem}`.")

            } else if(statusCode == 404) {
            println "[Error] Folder item not found."
            System.exit(1)
            } else {
            HttpEntity del_entity = response.getEntity()
            def delEnt = EntityUtils.toString(del_entity)
            String finalError

            if(delEnt.indexOf("</body>") > -1) {
            String errorResult = delEnt.split("<body>")[1];
            String errorResultFilter = errorResult.split("</body>")[0];
            finalError = "[Error] " + errorResultFilter
            } else {
            finalError = "[Error] Something went wrong ."
            }
            println finalError
            System.exit(1)
            }
        } catch (Exception e) {
            println "[Error] Failed folder item deletion."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public void configJobOptions() {
        try {
            configureJobOptions()
            println "Job options configured successfully."
        } catch (Exception e) {
            println "[Error] Failed configuration."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public String[] createScanV2() {
        try {
        if(autoScanName && autoScanName == 'Yes') {
            long unixTimeStamp = System.currentTimeMillis() + 12
            def timeStamp = new Date(unixTimeStamp)
            scanName = "${scanName} ${timeStamp}"
            println("Updated scan name to -> ${scanName}")
        }
            HttpGet templateRequest = new HttpGet(baseUrl + "ase/api/templates")
            def response = doRequestV2(templateRequest,null)
            HttpEntity r_entity = response.getEntity()
            def ent = EntityUtils.toString(r_entity)
            def jsonSlurper = new JsonSlurper()
            def responseObject = jsonSlurper.parseText(ent)
            Integer templateId
            for(def item: responseObject) {
            if(item.name == templateName) {
                templateId = item.id
                break;
            }
        }

        if(!templateId) {
            println "[Error] Incorrect template name or template does not exists."
            System.exit(1)
        }

        if(testPolicyId.isEmpty() || testPolicyId.length() == 0 || (testPolicyId =~ /^[0-9]+$/).matches() == false) {
            println "[Warning] Setting Test Policy ID value to 1, as no value or Invalid value is provided. "
            testPolicyId = "1"
        }
        String payload = "{ \"testPolicyId\": ${testPolicyId}, \"folderId\": ${folderID}, \"applicationId\": ${appID}, \"name\": \"${scanName}\", \"description\": \"${scanDescription}\", \"contact\": \"\" }"
        HttpPost createJobRequest = new HttpPost(baseUrl + "ase/api/jobs/" + templateId + "/dastconfig/createjob")
        def jobResponse = doRequestV2(createJobRequest,payload)

        def statusLine = jobResponse.getStatusLine()
        def statusCode = statusLine.getStatusCode()
            HttpEntity job_entity = jobResponse.getEntity()
            def jobEnt = EntityUtils.toString(job_entity)

        if(statusCode == 201) {
            def jobObject = jsonSlurper.parseText(jobEnt)
            this.scanFIID = jobObject.id
            println "jobObject => "+jobObject
            println "Scan job created successfully with job ID - "+this.scanFIID
            String[] outProps = [this.scanFIID,this.scanFIID+1,appID]
            return outProps
        } else {
            String finalError

            if(jobEnt.indexOf("</body>") > -1) {
            String errorResult = jobEnt.split("<body>")[1];
            String errorResultFilter = errorResult.split("</body>")[0];
            finalError = "[Error] " + errorResultFilter
            } else {
            finalError = "[Error] Something went wrong ."
            }
            println finalError
            System.exit(1)

        }

            } catch (Exception e) {
            println "[Error] Failed to create scan job."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public void configJobOptionsV2() {
        try {
        
        String scanTypeId
        if(scanType) {
            scanTypeId = scanType.toString()
        } else {
            scanTypeId = "1"
        }

        //Set recorded traffic
        if (recordedTraffic) {
            println "Updating the recorded traffic."
            println "scanType set to Test Only(ID: 3) as record traffic is provided."
            scanTypeId = "3" 
            File file = new File(recordedTraffic)
            HttpPost post = new HttpPost(baseUrl + "ase/api/jobs/" + this.scanFIID + "/dastconfig/updatetraffic/replace")
            FileBody fileBody = new FileBody(file)

            MultipartEntityBuilder builder = MultipartEntityBuilder.create()
            builder.addPart("uploadedfile", fileBody)

            HttpEntity entity = builder.build()
            post.setEntity(entity);
            HttpResponse fileResponse = client.execute(post)
            def fileStatusLine = fileResponse.getStatusLine()
            def fileStatusCode = fileStatusLine.getStatusCode()

        if(fileStatusCode == 200) {
        println "Traffic file uploaded successfully for Scan ID - " +  this.scanFIID
        } else {
        println "[Error] Something went wrong while updating the configuration."
        System.exit(1)

        }
        }

        // Update starting Url
        if(startUrl) {
            println "Updating the starting URL."
            def startingUrlPayload =  "{\"scantNodeXpath\":\"StartingUrl\",\"scantNodeNewValue\":\"${startUrl}\"}"
            HttpPost updateStartingUrlRequest = new HttpPost(baseUrl + "ase/api/jobs/" + this.scanFIID + "/dastconfig/updatescant")
            def startingUrlResponse = doRequestV2(updateStartingUrlRequest,startingUrlPayload)
            def startingUrlStatusLine = startingUrlResponse.getStatusLine()
            def startingUrlStatusCode = startingUrlStatusLine.getStatusCode()
            if(startingUrlStatusCode == 200) {
            println "Starting Url updated for Scan ID -" +  this.scanFIID
            } else {
            println "[Error] Something went wrong while updating the starting url."
            System.exit(1)

            }
        }
        
        if(loginMethod == "None" || loginMethod == "Automatic") {
            String[] updateConfigPayloadArray
            if(loginMethod == "None") {
        updateConfigPayloadArray = [
            "{\"scantNodeXpath\":\"LoginMethod\",\"scantNodeNewValue\":\"None\"}"]
            }
            if(loginMethod == "Automatic") {
        updateConfigPayloadArray = [
            "{\"scantNodeXpath\":\"LoginMethod\",\"scantNodeNewValue\":\"Automatic\"}",
            "{\"scantNodeXpath\":\"LoginUsername\",\"scantNodeNewValue\":\"${scanUser}\"}",
            "{ \"scantNodeXpath\":\"LoginPassword\",\"scantNodeNewValue\":\"${scanPassword}\" ,\"encryptNodeValue\":true}"]
            }
        HttpPost updateConfigRequest = new HttpPost(baseUrl + "ase/api/jobs/" + this.scanFIID + "/dastconfig/updatescant")
        def configStatusCode
        for(def payload: updateConfigPayloadArray) {
        def configResponse = doRequestV2(updateConfigRequest,payload)
        def configStatusLine = configResponse.getStatusLine()
        configStatusCode = configStatusLine.getStatusCode()

        }

        if(configStatusCode == 200) {
        println "Login updated for Scan ID -" +  this.scanFIID
        } else {
        println "[Error] Something went wrong while updating the configuration."
        System.exit(1)

        }

        }else {
        if (loginFile) {
            println "Uploading login file."
            File file = new File(loginFile)
            HttpPost post = new HttpPost(baseUrl + "ase/api/jobs/" + this.scanFIID + "/dastconfig/updatetraffic/login")
            FileBody fileBody = new FileBody(file)

            MultipartEntityBuilder builder = MultipartEntityBuilder.create()
            builder.addPart("uploadedfile", fileBody)

            HttpEntity entity = builder.build()
            post.setEntity(entity);
            HttpResponse fileResponse = client.execute(post)
            def fileStatusLine = fileResponse.getStatusLine()
            def fileStatusCode = fileStatusLine.getStatusCode()

        if(fileStatusCode == 200) {
        println "Login file uploaded successfully for Scan ID - " +  this.scanFIID
        } else {
        println "[Error] Something went wrong while updating the configuration."
        System.exit(1)

        }
        } else {
        println "[Error] Login file not provided."
        System.exit(1) 
        }
        }
        //Update ScanType Request
        HttpPut updateScanTypeRequest = new HttpPut(baseUrl + "ase/api/jobs/scantype?scanTypeId=" + scanTypeId + "&jobId=" + this.scanFIID)
        def scanTypeResponse = doRequestV2(updateScanTypeRequest,null)
        def scanTypeStatusLine = scanTypeResponse.getStatusLine()
        def scanTypeStatusCode = scanTypeStatusLine.getStatusCode()

        if(scanTypeStatusCode == 200) {
        println "scanType set to ID: ${scanTypeId}" 
        println "Scan type updated for Scan ID -" +  this.scanFIID
        } else {
        println "[Error] Something went wrong while updating scan type."
        System.exit(1)

        }

        } catch (Exception e) {
            println "[Error] Failed to update scan."
            e.printStackTrace()
            System.exit(1)
        }

    }

    public void runScanV2() {
        try {
        String etag
        HttpGet getJobRequest = new HttpGet(baseUrl + "ase/api/jobs/" + scanFIID)
         def jobResponse = doRequestV2(getJobRequest,null)
        def statusLine = jobResponse.getStatusLine()
        def statusCode = statusLine.getStatusCode()
        if(statusCode == 200) {
        def etagHeader = jobResponse.getHeaders("Etag")
        String etagString = etagHeader[0]
        etag = etagString.substring(6)

        } else if(statusCode == 404) {
            println "[Error] Scan ID not found."
            System.exit(1)
        } else {
            HttpEntity job_entity = jobResponse.getEntity()
            def jobEnt = EntityUtils.toString(job_entity)
            String finalError

            if(jobEnt.indexOf("</body>") > -1) {
            String errorResult = jobEnt.split("<body>")[1];
            String errorResultFilter = errorResult.split("</body>")[0];
            finalError = "[Error] " + errorResultFilter
            } else {
            finalError = "[Error] Something went wrong ."
            }
            println finalError
            System.exit(1)
        }

        String runScanPayload = "{\"type\":\"run\"}"
        HttpPost runScanRequest = new HttpPost(baseUrl + "ase/api/jobs/" + this.scanFIID + "/actions")
        def scanResponse = doRequestRunScanV2(runScanRequest,runScanPayload,etag)
        def scanStatusLine = scanResponse.getStatusLine()
        def scanStatusCode = scanStatusLine.getStatusCode()
        println "scanStatusCode -> "+scanStatusCode
        if(scanStatusCode == 200) {
            println "Scan ran successfully."
        } else if (scanStatusCode == 409) {
            println "[Error] Scan is already running."
            System.exit(1)  
        }
        else {
            HttpEntity scan_entity = scanResponse.getEntity()
            def scanEnt = EntityUtils.toString(scan_entity)
            String finalscanEntError

            if(scanEnt.indexOf("</body>") > -1) {
            String errorScanResult = scanEnt.split("<body>")[1];
            String errorScanResultFilter = errorScanResult.split("</body>")[0];
            finalscanEntError = "[Error] " + errorScanResultFilter
            } else {
            finalscanEntError = "[Error] Something went wrong ."
            }
            println finalscanEntError
            System.exit(1)
        }

        } catch (Exception e) {
            println "[Error] Failed running the scan."
            e.printStackTrace()
            System.exit(1)
        }
    }

    private HttpResponse doRequestRunScanV2(HttpUriRequest request , String contentString ,String etag) {
        request.addHeader("Content-Type", "application/json")
        request.addHeader("Accept", "*/*")
        request.addHeader("Accept-Encoding", "gzip, deflate, br")
        request.addHeader("If-Match", etag)

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

                if (contentString) {
            StringEntity input

            try {
                input = new StringEntity(contentString)
            }
            catch(UnsupportedEncodingException ex) {
                println("Unsupported characters in http request content: ${contentString}")
                System.exit(1)
            }

            request.setEntity(input)
        }
        HttpResponse response = client.execute(request)

        return response
    }

        private HttpResponse doRequestV2(HttpUriRequest request , String contentString) {
        request.addHeader("Content-Type", "application/json")
        request.addHeader("Accept", "*/*")
        request.addHeader("Accept-Encoding", "gzip, deflate, br")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

                if (contentString) {
            StringEntity input

            try {
                input = new StringEntity(contentString)
            }
            catch(UnsupportedEncodingException ex) {
                println("Unsupported characters in http request content: ${contentString}")
                System.exit(1)
            }

            request.setEntity(input)
        }
        HttpResponse response = client.execute(request)

        return response
    }

    private String[] createScanByTemplateID() {
        String createURL = baseUrl + "ase/services/"

        // Get templateFIID
        println "Retrieving the identifier of the " + templateName + " template..."
        Document doc = sendGetRequest(baseUrl + "ase/services/templates")
        String templateId = (String) xpath.evaluate("//content-scan-job[name='" + templateName + "']/id/text()", doc, XPathConstants.STRING)
        println "Template ID: ${templateId}"
        if (!templateId) {
            throw new RuntimeException("[Error] ID not found for template: ${templateName}")
        }

        if (folderID) {
            createURL += "folders/${folderID}/folderitems?templateid=${templateId}"
        }
        else {
            createURL += "folderitems?templateid=${templateId}"
        }
        if (appID) {
            createURL += "&appid=${appID}"
        }

        def encodedName = URLEncoder.encode(scanName, "UTF-8")
        encodedName = encodedName.replace('.', '%2E')
        encodedName = encodedName.replace('-', '%2D')

        println "Creating ${scanName} based on the ${templateName} template."
        def postData = "name=" + encodedName + "&description=" + URLEncoder.encode(scanDescription, "UTF-8")
        if (payload) {
            postData += "&payload=" + URLEncoder.encode(payload, "UTF-8")
        }
        doc = sendPostRequest(createURL, postData)
        println "Scan information:"

        String createdScanFIID = (String) xpath.evaluate("//folder-items/content-scan-job/id/text()", doc, XPathConstants.STRING)
        String createdReportsFIID = (String) xpath.evaluate("//folder-items/report-pack/id/text()", doc, XPathConstants.STRING)
        String createdReportsURL = (String) xpath.evaluate("//folder-items/report-pack/reports/@href", doc, XPathConstants.STRING)

        println "scanFIID: ${createdScanFIID}"
        println "reportsFIID: ${createdReportsFIID}"
        println "reportsURL: ${createdReportsURL}"

        String[] outProps = [createdScanFIID, createdReportsFIID, createdReportsURL]
        return outProps

    }

    private void configureJobOptions() {
        String scanURL = "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(baseUrl + scanURL)

        String optionsURL = (String) xpath.evaluate("//content-scan-job/options/@href", doc, XPathConstants.STRING)

        def postData
        // Set the starting URL
        if(startUrl) {
            println "Updating the starting URL."
            postData = "value=" + URLEncoder.encode(startUrl, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.STARTING_URLS, postData)
        }
        // Set automatic login
        if(setAuto == "true") {
            println "Enabling Automatic Login Method."
            postData = "value=3"
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN, postData)
        } else if(setAuto == "false") {
            println "Disabling Automatic Login Method."
            postData = "value=0"
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN, postData)
        }
        // Set the autoform username
        if(scanUser) {
            println "Updating Automatic Login username."
            postData = "value=" + URLEncoder.encode(scanUser, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN_USERNAME, postData)
        }
        // Set the autoform password
        if(scanPassword) {
            println "Updating Automatic Login password."
            postData = "value=" + URLEncoder.encode(scanPassword, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN_PASSWORD, postData)
        }
        // Set the http authentication
        if(httpAuth == "true") {
            println "Enabling HTTP authentication."
            postData = "value=" + URLEncoder.encode(httpAuth, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_AUTHENTICATION, postData)
        } else if(httpAuth == "false") {
            println "Disabling HTTP authentication."
            postData = "value=" + URLEncoder.encode(httpAuth, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_AUTHENTICATION, postData)
        }
        // Set the http username
        if(httpUser) {
            println "Updating the HTTP username."
            postData = "value=" + URLEncoder.encode(httpUser, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_USERNAME, postData)
        }
        // Set the http password
        if(httpPassword) {
            println "Updating the HTTP username."
            postData = "value=" + URLEncoder.encode(httpPassword, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_PASSWORD, postData)
        }

        //Set recorded traffic
        if (recordedTraffic) {
            println "Updating the recorded traffic."
            File file = new File(recordedTraffic)

            HttpPost postRequest = new HttpPost(scanURL + "/httptrafficdata")

            FileInputStream fileInputStream = new FileInputStream(file)
            InputStreamEntity reqEntity = new InputStreamEntity(fileInputStream, file.length())
            postRequest.setEntity(reqEntity);
            reqEntity.setContentType("binary/octet-stream");

            postRequest.addHeader("Content-Type", "application/octet-stream")
            postRequest.addHeader("Accept", "application/json,application/xml")

            HttpResponse response = client.execute(request)
            doc = convertToDOM(response)
            checkForError(doc)
        }

        // Set the scan limit
        if(scanLimit) {
            println "Updating the scan limit."
            postData = "value=" + URLEncoder.encode(scanLimit, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.SCAN_LIMIT, postData)
        }
    }

    private int runScanByFIID() throws Exception {
        int result = ScanState.UNKNOWN
        // Set scan URL for later use and pull out some information about the scan
        String reportPackURL = ""
        def reportPackLastRunDateTime = ""
        String scanURL = "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(baseUrl + scanURL)

        String scanLastRunDateTime = DocUtil.getContentScanJob_LastRun(doc)
        println "Scan last run: " + scanLastRunDateTime

        // Set report URL for later use and pull out some information about the report pack
        if(reportsFIID) {
            reportPackURL = "ase/services/folderitems/" + reportsFIID
            doc = sendGetRequest(baseUrl + reportPackURL)

            String reportsURL = (String) xpath.evaluate("//report-pack/reports/@href", doc, XPathConstants.STRING)
            println "Reports URL: " + reportsURL
            reportPackLastRunDateTime = xpath.evaluate("//report-pack/last-run/text()", doc, XPathConstants.STRING)
            println "Reports last run: " + reportPackLastRunDateTime
        }

        // Confirm Scan is ready to run
        Double scanState = (Double) xpath.evaluate("//state/id/text()", doc, XPathConstants.NUMBER)
        String status = (String) xpath.evaluate("//state/name/text()", doc, XPathConstants.STRING)
        println "[${new Date().toString()}] Status: " + status
        result = scanState.intValue()

        def postData
        // Stop scan if starting, running, or resuming
        if (result == ScanState.READY ) {
            // Run the scan by setting the action
            println "Running the scan."
            postData = "action=" + ScanAction.RUN
            doc = sendPostRequest(baseUrl + scanURL, postData)
        } else if (result == ScanState.RUNNING ) {
            println "Scan is already in the RUNNING state."
        } else {
            println "Scan is in an invalid '${status}' state."
        }

        if (waitForScanComp && (result == ScanState.READY || result == ScanState.RUNNING)) {
            // Wait for scan completion
            println "Waiting for scan completion..."
            result = waitForCompletion(baseUrl + scanURL, scanLastRunDateTime)
            if (result == ScanState.READY) {
                println "Scan completed successfully."
            } else if (result == ScanState.RUNNING) {
                println "Scan is still running."
            }
            // Wait for report pack completion
            if(reportsFIID) {
                if (result == ScanState.READY) {
                    println "Waiting for report pack completion..."
                    result = waitForCompletion(baseUrl + reportPackURL, reportPackLastRunDateTime)
                    println "Reports finished successfully."
                    getReportInfo()
                } else {
                    println "[Warning] Scan not complete. Ignoring report retrieval."
                }
            }
        } else {
            // Wait for running
            result = waitForRunning(baseUrl + scanURL, scanLastRunDateTime)
        }
        return result
    }

    private int stopScanByFIID() {
        int result = ScanState.UNKNOWN
        String fullURL = baseUrl + "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(fullURL)
        Double scanState = (Double) xpath.evaluate("//state/id/text()", doc, XPathConstants.NUMBER)
        String status = (String) xpath.evaluate("//state/name/text()", doc, XPathConstants.STRING)
        println "[${new Date().toString()}] Status: " + status
        result = scanState.intValue()
        // Stop scan if starting, running, or resuming
        if (result == ScanState.STARTING ||
            result == ScanState.RUNNING  ||
            result == ScanState.RESUMING ) {
            println "Stopping the scan."
            String postData = "action=" + ScanAction.END
            sendPostRequest(fullURL, postData)
            // Wait for scan to enter a ready state
            result = waitForReady(scanFIID)
        } else {
            println "Scan is stoppped."
        }
        return result
    }

    private def getReportInfo() {
        return getReportInfo(null)
    }

    private def getReportInfo(String reportName) {
        // Set report URL for later use and pull out some information about the report pack
        String reportPackURL = "ase/api/folderitems/" + reportsFIID + "/reports?folderItemReportsInEnglish=false"
        Document doc = sendGetRequest(baseUrl + reportPackURL)
        // Will determine whether or not the severity counts have been found
        Boolean haveSevs = false
        String criticalSevIssuesCount = ""
        String highSevIssuesCount = ""
        String mediumSevIssuesCount = ""
        String lowSevIssuesCount = ""

        String reportsURL = DocUtil.getReportPack_Reports_href(doc)
        println "Reports URL: " + reportsURL
        String reportPackLastRunDateTime = DocUtil.getReportPack_LastRun(doc)
        println "Report Pack Last Run: ${reportPackLastRunDateTime}"

        // Get summary report
        println "Retrieving report summary information..."
        doc = sendGetRequest(reportsURL)

        String fileSummaryName = reportsFIID + "-Summary.xml"
        File reportSummaryFile = new File(filepath + fileSummaryName)

        reportSummaryFile << getXmlString(doc)
        println "[Ok] Generated Specific Report XML: ${reportSummaryFile.getCanonicalPath()}"

        if (printReport) {
            println "=========================================="
            printOuterXml(doc)
            println "=========================================="
        }

        File reportNameFile
        String reportNameAllURL = ""
        if (reportName) {
            String reportNameURL = DocUtil.getReportName_href(doc, reportName)
            println "${reportName} Report URL: " + reportNameURL
            if (reportNameURL) {
                try {
                    criticalSevIssuesCount = DocUtil.getIssues_Count(doc, reportName, "Critical")
                    highSevIssuesCount = DocUtil.getIssues_Count(doc, reportName, "High")
                    mediumSevIssuesCount = DocUtil.getIssues_Count(doc, reportName, "Medium")
                    lowSevIssuesCount = DocUtil.getIssues_Count(doc, reportName, "Low")
                    if (criticalSevIssuesCount && highSevIssuesCount && mediumSevIssuesCount && lowSevIssuesCount) {
                        haveSevs = true
                        println "Acquired the count of all ${reportName} issues."
                    }
                } finally {
                    if (haveSevs == true) {
                        println "Critical Severity Issues: ${criticalSevIssuesCount}"
                        println "High Severity Issues: ${highSevIssuesCount}"
                        println "Medium Severity Issues: ${mediumSevIssuesCount}"
                        println "Low Severity Issues: ${lowSevIssuesCount}"
                    }
                    else {
                        println "[Warning] Could not acquire Critical, High, Medium, and Low Severity Issues Count."
                    }
                }

                String fileReportName = reportsFIID + "-" + reportName.replaceAll(' ', '-') + ".xml"
                reportNameFile = new File(filepath + fileReportName)

                // Retrieve report name data
                reportNameAllURL = reportNameURL + "?mode=all"
                println "Retrieving complete ${reportName} information..."
                doc = sendGetRequest(reportNameAllURL)

                reportNameFile << getXmlString(doc)

                if (printReport) {
                    println "=========================================="
                    printOuterXml(doc)
                    println "=========================================="
                }
                println "[Ok] Generated Specific Report XML: ${reportNameFile.getCanonicalPath()}"
            } else {
                println "[Error] Report name '${reportName}' could not be found."
            }
        }


        def outProps = [reportSummaryFile.getCanonicalPath(), reportNameFile?.getCanonicalPath()?:"",
            criticalSevIssuesCount, highSevIssuesCount, mediumSevIssuesCount, lowSevIssuesCount,
            reportsURL, reportNameAllURL]
        return outProps
    }

    private String[] getPDFReportInfo() {
        String body = ""
        if(pdfReportFileType == "Detailed") {
        if (scanName) {
            body = "{\"config\": {\"executiveSummaryIncluded\": true,\"advisoriesIncluded\": true,\"pdfPageBreakOnIssue\": false,\"sortByURL\": true,\"applicationAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]},\"issueConfig\": {\"includeAdditionalInfo\": true,\"variantConfig\": {\"variantLimit\": 2,\"requestResponseIncluded\": true,\"trafficCharactersCount\": 5000,\"differencesIncluded\": true},\"issueAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]}}},\"layout\": {\"reportOptionLayoutCoverPage\": {\"companyLogo\": \"\",\"additionalLogo\": \"\",\"includeDate\": false,\"includeReportType\": false,\"reportTitle\": \"\",\"description\": \"\"},\"reportOptionLayoutBody\": {\"header\": \"\",\"footer\": \"\"},\"includeTableOfContents\": false},\"reportFileType\": \"PDF\",\"issueIdsAndQueries\": [\"Scan Name=${scanName}\"]}"
        }
        else {
            body = "{\"config\": {\"executiveSummaryIncluded\": true,\"advisoriesIncluded\": true,\"pdfPageBreakOnIssue\": false,\"sortByURL\": true,\"applicationAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]},\"issueConfig\": {\"includeAdditionalInfo\": true,\"variantConfig\": {\"variantLimit\": 2,\"requestResponseIncluded\": true,\"trafficCharactersCount\": 5000,\"differencesIncluded\": true},\"issueAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]}}},\"layout\": {\"reportOptionLayoutCoverPage\": {\"companyLogo\": \"\",\"additionalLogo\": \"\",\"includeDate\": false,\"includeReportType\": false,\"reportTitle\": \"\",\"description\": \"\"},\"reportOptionLayoutBody\": {\"header\": \"\",\"footer\": \"\"},\"includeTableOfContents\": false},\"reportFileType\": \"PDF\",\"issueIdsAndQueries\": [\"\"]}"
        }
        } else {
        if (scanName) {
            body = "{\"config\": {\"executiveSummaryIncluded\": false,\"advisoriesIncluded\": false,\"pdfPageBreakOnIssue\": false,\"sortByURL\": false,\"applicationAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]},\"issueConfig\": {\"includeAdditionalInfo\": false,\"variantConfig\": {\"variantLimit\": 0,\"requestResponseIncluded\": false,\"differencesIncluded\": false},\"issueAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]}}},\"layout\": {\"reportOptionLayoutCoverPage\": {\"companyLogo\": \"\",\"additionalLogo\": \"\",\"includeDate\": false,\"includeReportType\": false,\"reportTitle\": \"\",\"description\": \"\"},\"reportOptionLayoutBody\": {\"header\": \"\",\"footer\": \"\"},\"includeTableOfContents\": false},\"reportFileType\": \"PDF\",\"issueIdsAndQueries\": [\"Scan Name=${scanName}\"]}"
        }
        else {
            body = "{\"config\": {\"executiveSummaryIncluded\": false,\"advisoriesIncluded\": false,\"pdfPageBreakOnIssue\": false,\"sortByURL\": false,\"applicationAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]},\"issueConfig\": {\"includeAdditionalInfo\": false,\"variantConfig\": {\"variantLimit\": 0,\"requestResponseIncluded\": false,\"differencesIncluded\": false},\"issueAttributeConfig\": {\"showEmptyValues\": false,\"attributeLookups\": [\"\"]}}},\"layout\": {\"reportOptionLayoutCoverPage\": {\"companyLogo\": \"\",\"additionalLogo\": \"\",\"includeDate\": false,\"includeReportType\": false,\"reportTitle\": \"\",\"description\": \"\"},\"reportOptionLayoutBody\": {\"header\": \"\",\"footer\": \"\"},\"includeTableOfContents\": false},\"reportFileType\": \"PDF\",\"issueIdsAndQueries\": [\"\"]}"
        }   
        }

        StringEntity params = new StringEntity(body)


        def securityDetailURL = "ase/api/issues/reports/securitydetails?appId=" + applicationID

        HttpPost securityDetailRequest = new HttpPost(baseUrl + securityDetailURL)
        HttpResponse response = doRequest(securityDetailRequest, params)

        def statusLine = response.getStatusLine()
        def statusCode = statusLine.getStatusCode()

        if (statusCode != 202) {
            println("[Error] Generation failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(response.entity?.content?.text)
        }
        else {
            println("[OK] Report generation was successful.")
        }
        def locations = response.getHeaders("Location")
        String location = locations[0]
        location = location.substring(10)
        println ("Report status location: " + location)

        HttpGet statusRequest = new HttpGet(location)
        // Get report status
        while (statusCode != 201) {

            response = doRequest(statusRequest)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()

            if (statusCode == 201) {
                println("[OK] Report ready.")
            }
            else if (statusCode != 200) {
                println("[Error] Report failure with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            }
            else {
                println "In progress"
            }
            Thread.sleep(5000)
        }
        locations = response.getHeaders("Location")
        location = locations[0]
        String reportURL = location.substring(10)
        println ("Report location: " + reportURL)

        // Get report
        HttpGet reportRequest = new HttpGet(reportURL)
        response = doPDFRequest(reportRequest)
        statusLine = response.getStatusLine()
        statusCode = statusLine.getStatusCode()

        if (statusCode != 200) {
            println("[Error] Download failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(response.entity?.content?.text)
        }
        else {
            println("")
        }

        // Create file
        Format formatter = new SimpleDateFormat("ddMMMyyyy-HHmmss");
        String date = formatter.format(new Date());
        def filename = "AppScanReportOutput-" + date + ".zip"
        File file = new File(filepath + filename)
        String fullFileName = file.getCanonicalPath()

        outputZipFile(response, file)

        return [fullFileName, reportURL]
    }

    private void outputZipFile(def response, File file) {
        // Download file in a stream
        InputStream input = null
        FileOutputStream output = null
        byte[] buffer = new byte[1024]
        try {
            input = response.getEntity().getContent()
            output = new FileOutputStream(file)
            for (int length; (length = input.read(buffer)) > 0;) {
                output.write(buffer, 0, length)
            }
        } finally {
            if (output != null) try { output.close(); } catch (IOException logOrIgnore) {}
            if (input != null) try { input.close(); } catch (IOException logOrIgnore) {}
        }
        println "[Ok] Generated Report Zip: ${file.getCanonicalPath()}"
    }

    private static String getXmlString(Document response) throws Exception {
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        StringWriter writer = new StringWriter()
        transformer.transform(new DOMSource(response.getDocumentElement()), new StreamResult(writer))
        return writer.toString()
    }

    // creates a new LW-SSO token
    private String authenticate() {
        HttpPost loginRequest
        HttpResponse response
        def statusLine
        def statusCode
        String sessionId = null
        // Authentication headers tacked on by HttpClientBuilder
            String auth = "{\"userId\":\"${user.replace("\\","\\\\")}\",\"password\":\"${password}\",\"featureKey\":\"AppScanEnterpriseUser\"}"
            StringEntity params = new StringEntity(auth)

            loginRequest = new HttpPost(baseUrl + "ase/api/login")
            response = doRequest(loginRequest, params)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()

            if (statusCode != 200) {
                println("[Error] Authentication failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            }
            else {
                println("[OK] Authentication was successful.")
            }

            HttpEntity r_entity = response.getEntity()
            def ent = EntityUtils.toString(r_entity)
            String [] resp = ent.split("\"")
            sessionId = resp[5]

        return sessionId
    }

    // Execute a general http request
    private HttpResponse doRequest(HttpUriRequest request) {
        return doRequest(request, null)
    }

    // Execute a general http request
    private HttpResponse doPDFRequest(HttpUriRequest request) {
        request.addHeader("Content-Type", "application/octet-stream")
        request.addHeader("Accept", "application/json,application/xml,application/octet-stream")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }
        HttpResponse response = client.execute(request)

        return response
    }

    // Specify a json string to provide a string entity to the http request
    private HttpResponse doRequest(HttpUriRequest request, String contentString) {
        request.addHeader("Content-Type", "application/x-www-form-urlencoded")
        request.addHeader("Accept", "application/json,application/xml")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

        if (contentString) {
            StringEntity input

            try {
                input = new StringEntity(contentString)
            }
            catch(UnsupportedEncodingException ex) {
                println("Unsupported characters in http request content: ${contentString}")
                System.exit(1)
            }

            request.setEntity(input)
        }

        HttpResponse response = client.execute(request)

        return response
    }

    // Specify a json string to provide a string entity to the http request
    private HttpResponse doRequest(HttpUriRequest request, StringEntity contentEntity) {
        request.addHeader("Content-Type", "application/json")
        request.addHeader("Accept", "application/json,application/xml")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

        request.setEntity(contentEntity);

        HttpResponse response = client.execute(request)

        return response
    }

    private Document sendGetRequest(String url) {
        HttpGet getRequest = new HttpGet(url)
        def response = doRequest(getRequest)
        Document doc = convertToDOM(response)
        checkForError(doc)
        return doc
    }

    private HttpResponse sendGetRequestV2(String url) {
        HttpGet getRequest = new HttpGet(url)
        def response = doRequest(getRequest, null)
        return response        
    }

    private Document sendPostRequest(String url, def postData) {
        HttpPost postRequest = new HttpPost(url)
        def response = doRequest(postRequest, postData)
        Document doc = convertToDOM(response)
        checkForError(doc)
        return doc
    }

    private Document sendDeleteRequest(String url) {
        HttpDelete deleteRequest = new HttpDelete(url)
        def response = doRequest(deleteRequest)
        int code = response.getStatusLine().getStatusCode()
        Document doc = null
        if (code < 200 || code >= 300){
            doc = convertToDOM(response)
            checkForError(doc)
            throw new Exception("[Error] Unexpected error from Delete Folder Item.")
        }
        return doc
    }

    private int waitForRunning(String url, String lastRunDate) throws Exception {
        int result = ScanState.UNKNOWN
        String prevStatus = ""
        String lastRunDateTime
        long startTime = System.currentTimeMillis()
        while (System.currentTimeMillis() < startTime + timeout) {
            Document doc = sendGetRequest(url)

            // Get the status and print it
            String status = (String) xpath.evaluate("//state/name/text()", doc, XPathConstants.STRING)
            if (!status.equals(prevStatus)) {
                println "[${new Date().toString()}] Status: " + status
                prevStatus = status
            }

            Double scanState = (Double) xpath.evaluate("//state/id/text()", doc, XPathConstants.NUMBER)
            result = scanState.intValue()
            // If manually stopped, user must "Save Current Results and Stop".
            // Otherwise, last runtime is not saved and infinite loop occurs.
            if (result == ScanState.READY) {
                // If scan marked ready, check last run time to verify completion
                // This means the scan fully ran within a 10 second sleep time...
                lastRunDateTime = (String) xpath.evaluate("//last-run/text()", doc, XPathConstants.STRING)
                if (lastRunDateTime.compareTo(lastRunDate) != 0) {
                    println "Completed successfully."
                    break
                }
            } else if (result == ScanState.STARTING) {
                // Scan is STARTING, wait for RUNNING
            } else if (result == ScanState.RUNNING) {
                println "Scan is running."
                break
            } else {
                result = scanState.intValue()
                break
            }
            Thread.sleep(5000)
        }
        if (result != ScanState.RUNNING ||
            (result == ScanState.READY && (lastRunDateTime.compareTo(lastRunDate) == 0))) {
            println ("[Error] The scan runtime has exceeded the specified timeout while waiting for RUNNING.")
        }
        return result
    }

    public String[] waitForCompletionV2() throws Exception {
        long startTime = System.currentTimeMillis()
        String scanStatus
        while (System.currentTimeMillis() < startTime + timeout) {

            HttpGet folderRequest = new HttpGet(baseUrl + "ase/api/folderitems/"+scanFIID)
            def response = doRequestV2(folderRequest,null)
            HttpEntity r_entity = response.getEntity()
            def statusLine = response.getStatusLine()
            def statusCode = statusLine.getStatusCode()
            def folderResult = EntityUtils.toString(r_entity)
            if(statusCode == 200) {
            def jsonSlurper = new JsonSlurper()
            def responseObject = jsonSlurper.parseText(folderResult)
            scanStatus = responseObject["content-scan-job"].state.name
            if(scanStatus.indexOf("Ready") > -1)
            {
                println "Status : "+scanStatus
                break
            }
            if(scanStatus.indexOf("Suspended") > -1)
            {
                println "Status : "+scanStatus
                break
                System.exit(1)

            }
            } else if(statusCode == 404) {
                println "[Error] Scan ID not found."
                System.exit(1)
            } else {
            String finalError

            if(folderResult.indexOf("</body>") > -1) {
            String errorResult = folderResult.split("<body>")[1];
            String errorResultFilter = errorResult.split("</body>")[0];
            finalError = "[Error] " + errorResultFilter
            } else {
            finalError = "[Error] Something went wrong ."
            }
            println finalError
            System.exit(1)
            }

        Thread.sleep(5000)
        }
        if (scanStatus != "Ready") {
            println ("[Error] The scan runtime has exceeded the specified timeout while waiting for COMPLETION.")
        }
        String[] outProps = [scanStatus,scanFIID]
        return outProps
    }

    private int waitForCompletion(String url, String lastRunDate) throws Exception {
        int result = ScanState.UNKNOWN
        String prevStatus = ""
        long startTime = System.currentTimeMillis()
        while (System.currentTimeMillis() < startTime + timeout) {
            Document doc = sendGetRequest(url)

            // Get the status and print it
            String status = DocUtil.getState_Name(doc)
            if (!status.equals(prevStatus)) {
                println "[${new Date().toString()}] Status: " + status
                prevStatus = status
            }

            Double scanState = DocUtil.getState_Id(doc)
            result = scanState.intValue()
            // If manually stopped, user must "Save Current Results and Stop".
            // Otherwise, last runtime is not saved and infinite loop occurs.
            if (result == ScanState.READY) {
                // If scan marked ready, check last run time to verify completion
                String lastRunDateTime = DocUtil.getLastRun(doc)

                if (lastRunDateTime.compareTo(lastRunDate) != 0) {
                    println "Completed successfully."
                    break
                }

            } else if (result == ScanState.SUSPENDED) {
                // An error has occurred
                println "Object has suspended. Check AppScan Enterprise Console for more information on the scan: "
                if(baseUrl.endsWith('ase/')) {
                    print baseUrl + "Jobs/JobStatistics.aspx?fiid=" + scanFIID
                }
                break
            } else if (result == ScanState.CANCELING) {
                // The scan was cancelled
                println "Object is canceling."
                break
            }

            //Thread.sleep(calculateSleep(startTime + timeout))
            Thread.sleep(5000)
        }
        if (result != ScanState.READY) {
            println ("[Error] The scan runtime has exceeded the specified timeout while waiting for COMPLETION.")
        }
        return result
    }

    // Sleep a different amount based on distance from timeout
    private long calculateSleep(endTime) {
        long result = 10000
        long remTime = endTime - System.currentTimeMillis()
        float percRem = remTime / timeout
        // Greater than 95% remaining
        if (percRem > 0.95) {
            result = 15000
        // Greater than 90% remaining
        } else if (percRem > 0.90) {
            result = 60000
        // Greater than 10% remaining
        } else if (percRem > 0.1) {
            result = 120000
        } else {
            result = 60000
        }
        return result
    }

    private int waitForReady(String fiid) throws Exception {
        int result = ScanState.UNKNOWN
        int retries = 0
        String prevStatus = ""
        long startTime = System.currentTimeMillis()
        long twelveHours = 43200000 // timeout is twelve hours
        while (System.currentTimeMillis() < startTime + twelveHours) {
            Document doc = sendGetRequest(baseUrl + "ase/services/folderitems/" + fiid)

            // Get the status and print it
            String status = DocUtil.getState_Name(doc)
            if (!status.equals(prevStatus)) {
                println "[${new Date().toString()}] Status: " + status
                prevStatus = status
                retries = 0 // reset retries if new status is found
            }

            Double scanState = DocUtil.getState_Id(doc)
            result = scanState.intValue()
            if (result == ScanState.READY) {
                println "Scan or Report is in READY state."
                break
            } else if (result == ScanState.SUSPENDED || result == ScanState.CANCELING) {
                // An error has occurred
                println "Scan or report is entering a failed state. Check AppScan Enterprise Console for more information: "
                if(baseUrl.endsWith('ase/')) {
                    print baseUrl + "Jobs/JobStatistics.aspx?fiid=" + fiid
                }
                break
            }
            retries++
            Thread.sleep(30000)
        }
        if (result != ScanState.READY) {
            println ("[Error] The scan or report has exceeded the timeout (12 hours) while waiting for READY.")
        }
        return result
    }

    // Checks response for errors
    private void checkForError(Document doc) throws Exception {
        if((String) xpath.evaluate("//error/text()", doc, XPathConstants.STRING)) {

            String code = (String) xpath.evaluate("//code/text()", doc, XPathConstants.STRING)
            String message = (String) xpath.evaluate("//message/text()", doc, XPathConstants.STRING)
            String help = (String) xpath.evaluate("//error/help/@weblink", doc, XPathConstants.STRING)

            // Ignore selected errors
            // An object with the specified name has already been created. Provide a unique name.
            // Occurs when specifying duplicate URL for Configure Job
            if (code == "CRWAE0014E") {
                println ("[Warning] " + code + " : " + message)
                return
            }

            // If code does not match ignored errors, fail
            println "*** Error Occurred ***"
            if(code) {
                println code
            }
            if(message) {
                println message
            }
            if(help) {
                println help
            }
            throw new Exception("Unexpected error.")
        }
    }

    private static void printOuterXml(Document response) throws Exception {
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        transformer.setOutputProperty("omit-xml-declaration", "yes")

        StringWriter writer = new StringWriter()
        transformer.transform(new DOMSource(response.getDocumentElement()), new StreamResult(writer))
        println writer.toString()
    }

    private static Document convertToDOM(HttpResponse response) {
        HttpEntity r_entity = response.getEntity()
        def ent = EntityUtils.toString(r_entity)
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new StringBufferInputStream(ent)) //here
        return doc
    }

    private static NamespaceContext _nsContext
    static {
        _nsContext = new NamespaceContext() {
            public String getNamespaceURI(String prefix) {
                if (prefix.equalsIgnoreCase("ase"))
                    return "http://www.ibm.com/Rational/AppScanEnterprise"
                return XMLConstants.NULL_NS_URI
            }

            public String getPrefix(String arg0) {
                return null
            }

            public Iterator<?> getPrefixes(String arg0) {
                return null
            }
        }
    }

    private class ScanAction {
        public static int RUN = 2
        public static int SUSPEND = 3
        public static int CANCEL = 4
        public static int END = 5
    }
    private class OptionType {
        public static String STARTING_URLS = "epcsCOTListOfStartingUrls"
        public static String AUTOMATIC_LOGIN = "elCOTLoginSequenceType"
        public static String AUTOMATIC_LOGIN_USERNAME = "esCOTAutoFormFillUserNameValue"
        public static String AUTOMATIC_LOGIN_PASSWORD = "esCOTAutoFormFillPasswordValue"
        public static String HTTP_AUTHENTICATION = "ebCOTHttpAuthentication"
        public static String HTTP_USERNAME = "esCOTHttpUser"
        public static String HTTP_PASSWORD = "esCOTHttpPassword"
        public static String SCAN_LIMIT = "elCOTScanLimit"
    }

    private Map<String, Integer> ScanState = [
        READY: 1,
        STARTING: 2,
        RUNNING: 3,
        RESUMING: 6,
        CANCELING: 7,
        SUSPENDING: 8,
        SUSPENDED: 9,
        POSTPROCESSING: 10,
        ENDING: 12,
        UNKNOWN: -1 // Not a real AppScan ScanState
    ]

    private String getKey(int value){
        for(String key:ScanState.keySet()){
            if(ScanState.get(key).equals(value)) {
                return key
            }
        }
        return "UNKNOWN";
      }
}
