/**
 * BurpSuite JavaScript Security Extension
 * Copyright (C) 2019  Focal Point Data Risk, LLC
 * Written by: Peter Hefley
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General 
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program.  
 * If not, see <https://www.gnu.org/licenses/>.
 */
package org.focalpoint.isns.burp.srichecks;

import java.net.URL;
import java.net.MalformedURLException;

// For using the burp HTTP interface
import burp.IHttpService;
import burp.IExtensionHelpers;
import burp.IBurpExtenderCallbacks;
import burp.IHttpRequestResponse;
import burp.IResponseInfo;

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.URI;


import java.util.Arrays;

public class Requester {
    private IHttpService burpHttpService = null;
    private String urlString;
    private URL urlObj;
    private IHttpRequestResponse rr;
    private IExtensionHelpers myHelpers = null;
    private IBurpExtenderCallbacks myCallbacks = null;
    private short statusCode = 0;
    private String responseBody = "";
    private byte[] responseBodyBytes = null;
    public static final String NO_DATA_RECEIVED = "NO DATA NO DATA NO DATA";
    public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";

    /**
     * Public constructor for Requester objects
     * @param   callbacks  the burp suite callbacks object
     * @param   url        a String containing the URL you'll want to request
     * @return             a new Requestor object
     */
    public Requester(IBurpExtenderCallbacks callbacks, String url){
        setCallbacks(callbacks);
        setUrl(url);
        makeService();
        makeRequest();
    }

    /**
     * Set the URL which the requestor will pull
     * @param  url  a String of the URL to obtain
     */
    public void setUrl(String url){
        urlString = url;
        try {
            urlObj = new URL(url);
            makeService();
        }
        catch (MalformedURLException exception){
            System.err.println("[JS-SRI][-] Could not parse URL " + url);
        }
    }

    /**
     * Set the callbacks object to link back to burp suite
     * @param  callbacks  the callbacks object provided to the burp extension
     */
    public void setCallbacks(IBurpExtenderCallbacks callbacks){
        myCallbacks = callbacks;
        if (myCallbacks == null){
            myHelpers = null;
        } else {
            myHelpers = myCallbacks.getHelpers();
        }
    }


    /**
     * Generate the HTTP service required to use the Burp HTTP interface
     */
    public void makeService(){
        if (!(myHelpers == null)){
            Boolean useHttps = (urlObj.getProtocol().equals("https"));
            int port = 0;
            if (urlObj.getPort() == -1){
                if (urlObj.getProtocol().equals("https")){
                    port = 443;
                }
                if (urlObj.getProtocol().equals("http")){
                    port = 80;
                }
            }
            else {
                port = urlObj.getPort();
            }
            burpHttpService = myHelpers.buildHttpService(urlObj.getHost(), port, useHttps);
        }
    }

    /** 
     * Make the HTTP request this object is set up for
     */
    public void makeRequest(){
        if (myCallbacks == null){
            makeRequestWithoutBurp();
        } else {
            makeRequestWithBurp();
        }
    }

    /**
     * Make the HTTP request via burp
     */
    public void makeRequestWithBurp(){
        try {
            byte[] requestBytes = myHelpers.buildHttpRequest(urlObj);
            rr = myCallbacks.makeHttpRequest(burpHttpService, requestBytes);
            if (rr.getResponse() == null){
                responseBody = NO_DATA_RECEIVED;
            }
            else {
                IResponseInfo responseObj = myHelpers.analyzeResponse(rr.getResponse());
                statusCode = responseObj.getStatusCode();
                if (statusCode == 200){
                    responseBodyBytes = Arrays.copyOfRange(rr.getResponse(), responseObj.getBodyOffset(), rr.getResponse().length);
                    responseBody = myHelpers.bytesToString(responseBodyBytes);
                }
                else {
                    responseBody = NO_DATA_RECEIVED;
                }
            }
        }
        catch (Exception e){
            System.err.println("[-] There was an issue getting the JavaScript file at " + urlString);
            e.printStackTrace();
            responseBody = NO_DATA_RECEIVED;
        }
    }

    /**
     * Make the HTTP request without using the burp callbacks interface
     */
    public void makeRequestWithoutBurp(){
        HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(urlString))
            .build();
        try {
            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
            statusCode = (short) response.statusCode();
            responseBody = response.body();
            responseBodyBytes = response.body().getBytes();
        }
        catch (Exception ex) {
            System.err.println("[-] There was an issue getting the JavaScript file at " + urlString);
            ex.printStackTrace();
            responseBody = NO_DATA_RECEIVED;
        }
    }

    /**
     * Get the HTTP status code that was provided
     * @return  HTTP status code as a short
     */
    public short getStatusCode(){
        return statusCode;
    }

    /**
     * Get the response body from the request
     * @return  the HTTP response body as a String
     */
    public String getResponseBody(){
        return responseBody;
    }

    /**
     * Get the response body from the request
     * @return  the HTTP response body as an array of bytes
     */
    public byte[] getResponseBodyBytes(){
        return responseBodyBytes;
    }
}