// Code taken from https://github.com/PortSwigger/distribute-damage/blob/866eb5a42e455a52b9d9fe553d0bd6527b86b72b/src/burp/Utilities.java
// License: Apache2

//Copyright 2016 PortSwigger Web Security
//Copyright 2016 James Kettle <[email protected]>

package burp;
import burp.*;

import burp.GlobalVars;
import java.awt.*;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.text.NumberFormatter;


// ###########################################################################
// The class that actually holds the config
// ###########################################################################
public class Config {
    private LinkedHashMap<String, String> settings;
    private LinkedHashMap<String, String> readableNames;
    private NumberFormatter onlyInt;

    public Config() {
        settings = new LinkedHashMap<>();
        // These put()s determine the order shown on the settings screen
		// Note that the default values are documented on the website, so update accordingly
        put("enable", true);
        put("apikey", "none");
        put("classblacklist", "");
		put("hashtrace", true);
		put("issuepercve", false);
        put("debug", false);
        put("logdups", false);
        put("issuetitle", "Stack Trace Fingerprint Found");
        put("apiurl", "https://beanstack.io/api/");

        // NOTE: when editing these names, also update the documentation!
        readableNames = new LinkedHashMap<>();
        readableNames.put("enable", "Enable Lookups");
        readableNames.put("apiurl", "API URL");
        readableNames.put("issuetitle", "Issue Title");
        readableNames.put("debug", "Print Debug Messages to Stdout");
        readableNames.put("logdups", "Log Duplicates");
        readableNames.put("apikey", "API Key");
        readableNames.put("classblacklist", "Blacklisted Class Prefixes");
		readableNames.put("issuepercve", "Create an issue for each CVE*");
        readableNames.put("hashtrace", "Hash traces before submission*");

        for (String key: settings.keySet()) {
            //callbacks.saveExtensionSetting(key, null); // purge saved settings
            String value = GlobalVars.callbacks.loadExtensionSetting(key);
            if (GlobalVars.callbacks.loadExtensionSetting(key) != null) {
                putRaw(key, value);
            }
        }

        NumberFormat format = NumberFormat.getInstance();
        onlyInt = new NumberFormatter(format);
        onlyInt.setValueClass(Integer.class);
        onlyInt.setMinimum(-1);
        onlyInt.setMaximum(Integer.MAX_VALUE);
        onlyInt.setAllowsInvalid(false);
    }

    private Config(Config base) {
        settings = new LinkedHashMap<>(base.settings);
        onlyInt = base.onlyInt;
    }

    void printSettings() {
        GlobalVars.debug("printSettings():");
        for(String key: settings.keySet()) {
            GlobalVars.debug("  - " + getType(key) + " " + key + " = " + settings.get(key));
        }
    }

    public static JFrame getBurpFrame()
    {
        for(Frame f : Frame.getFrames())
        {
            if(f.isVisible() && f.getTitle().startsWith(("Burp Suite")))
            {
                return (JFrame) f;
            }
        }
        return null;
    }

    private String encode(Object value) {
        String encoded;
        if (value instanceof Boolean) {
            encoded = String.valueOf(value);
        }
        else if (value instanceof Integer) {
            encoded = String.valueOf(value);
        }
        else {
            encoded = "\"" + ((String) value).replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
        }
        return encoded;
    }

    private void putRaw(String key, String value) {
        settings.put(key, value);
    }

    private void put(String key, Object value) {
        settings.put(key, encode(value));
    }

    public void putAndSave(String key, Object value) {
        settings.put(key, encode(value));
        GlobalVars.callbacks.saveExtensionSetting(key, encode(value));
    }

    public String getString(String key) {
        String decoded = settings.get(key);
        decoded = decoded.substring(1, decoded.length()-1).replace("\\\"", "\"").replace("\\\\", "\\");
        return decoded;
    }

    public int getInt(String key) {
        return Integer.parseInt(settings.get(key));
    }

    public boolean getBoolean(String key) {
        String val = settings.get(key);
        if (val.equals("true") ) {
            return true;
        }
        else if (val.equals("false")){
            return false;
        }
        throw new RuntimeException();
    }

    String getType(String key) {
        String val = settings.get(key);
        if (val.equals("true") || val.equals("false")) {
            return "boolean";
        }
        else if (val.startsWith("\"")) {
            return "string";
        }
        else {
            return "number";
        }
    }

    void showSettings() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(0, 2));

        JLabel lbl = new JLabel("<html>" + GlobalVars.EXTENSION_NAME_SHORT + " settings (<a href=''>documentation</a>)</html>");
        lbl.setCursor(new Cursor(Cursor.HAND_CURSOR));
        panel.add(lbl);

        lbl.addMouseListener(new java.awt.event.MouseAdapter() {
            @Override public void mousePressed(java.awt.event.MouseEvent ev) {
                try {
                    java.awt.Desktop.getDesktop().browse(new java.net.URI(GlobalVars.SETTINGDOCURL));
                }
                catch (IOException|java.net.URISyntaxException e) {
                    e.printStackTrace();
                }
            }
        });

        panel.add(new JLabel());

        HashMap<String, Object> configured = new HashMap<>();

        for(String key: settings.keySet()) {
            String type = getType(key);
            panel.add(new JLabel("\n" + readableNames.get(key) + ": "));

            if (type.equals("boolean")) {
                JCheckBox box = new JCheckBox();
                box.setSelected(getBoolean(key));
                panel.add(box);
                configured.put(key, box);
            }
            else if (type.equals("number")){
                JTextField box = new JFormattedTextField(onlyInt);
                box.setText(String.valueOf(getInt(key)));
                panel.add(box);
                configured.put(key, box);
            }
            else {
                JTextField box = new JTextField(getString(key));
                panel.add(box);
                configured.put(key, box);
            }
        }

		panel.add(new JLabel("\n* Only available with an API key"));
		panel.add(new JLabel());

        int result = JOptionPane.showConfirmDialog(getBurpFrame(), panel, GlobalVars.EXTENSION_NAME + " settings", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
        if (result == JOptionPane.OK_OPTION) {
            for(String key: configured.keySet()) {
                Object val = configured.get(key);
                if (val instanceof JCheckBox) {
                    val = ((JCheckBox) val).isSelected();
                }
                else if (val instanceof JFormattedTextField) {
                    val = Integer.parseInt(((JFormattedTextField) val).getText().replace(",", ""));
                }
                else {
                    val = ((JTextField) val).getText();
                    if (key.equals("apiurl") && ! ((String)val).endsWith("/")) {
                        val += "/";
                    }
                }
                put(key, val);
                GlobalVars.callbacks.saveExtensionSetting(key, encode(val));
            }

            GlobalVars.debug("Saved settings.");
            printSettings();
        }
        else {
            GlobalVars.debug("Settings cancelled.");
        }
    }
}

// ###########################################################################
// Class to add context menu (right click menu) actions and handle them
// ###########################################################################
class ContextMenuSettingsOptionAdder implements IContextMenuFactory, ActionListener {
    @Override
    public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
        ContextMenuSettingsOptionAdder outer = this;
        // 16 is an undocumented magic number that indicates it was invoked
        // from the Issues list in the Target tab. The place where our events
        // are logged, so that seemed a logical place for the options button.
        // The number was reverse engineered using the highly advanced method
        // of println()ing getToolFlag and right clicking the desired place.
        return invocation.getToolFlag() == 16 ? new ArrayList<JMenuItem>() {{
            add(new JMenuItem(GlobalVars.EXTENSION_NAME_SHORT + " settings") {{
                addActionListener(outer);
            }});
        }} : null;
    }

    public void actionPerformed(ActionEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run(){
                GlobalVars.config.showSettings();
            }
        });
    }
}