Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to export as HAR File #114

Merged
merged 7 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class ExportController {
private final Preferences preferences;
private final HashMap<Class<? extends LogExporter>, LogExporter> exporters;
private final List<AutomaticLogExporter> enabledExporters;
public ExportController(LoggerPlusPlus loggerPlusPlus, Preferences preferences){

public ExportController(LoggerPlusPlus loggerPlusPlus, Preferences preferences) {
this.loggerPlusPlus = loggerPlusPlus;
this.preferences = preferences;

Expand All @@ -26,9 +26,10 @@ public ExportController(LoggerPlusPlus loggerPlusPlus, Preferences preferences){
initializeExporters();
}

private void initializeExporters(){
private void initializeExporters() {
this.exporters.put(CSVExporter.class, new CSVExporter(this, preferences));
this.exporters.put(JSONExporter.class, new JSONExporter(this, preferences));
this.exporters.put(HARExporter.class, new HARExporter(this, preferences));
this.exporters.put(Base64Exporter.class, new Base64Exporter(this, preferences));
this.exporters.put(ElasticExporter.class, new ElasticExporter(this, preferences));
}
Expand All @@ -51,13 +52,13 @@ public void disableExporter(AutomaticLogExporter logExporter) throws Exception {
logExporter.shutdown();
}

public void exportNewEntry(LogEntry logEntry){
public void exportNewEntry(LogEntry logEntry) {
for (AutomaticLogExporter exporter : this.enabledExporters) {
exporter.exportNewEntry(logEntry);
}
}

public void exportUpdatedEntry(LogEntry logEntry){
public void exportUpdatedEntry(LogEntry logEntry) {
for (AutomaticLogExporter exporter : this.enabledExporters) {
exporter.exportUpdatedEntry(logEntry);
}
Expand Down
83 changes: 83 additions & 0 deletions src/main/java/com/nccgroup/loggerplusplus/exports/HARExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.nccgroup.loggerplusplus.exports;

import com.coreyd97.BurpExtenderUtilities.Preferences;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.nccgroup.loggerplusplus.logentry.LogEntry;
import com.nccgroup.loggerplusplus.util.Globals;
import com.nccgroup.loggerplusplus.util.MoreHelp;
import com.nccgroup.loggerplusplus.util.SwingWorkerWithProgressDialog;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Type;
import java.util.List;

public class HARExporter extends LogExporter implements ExportPanelProvider, ContextMenuExportProvider {

private final HARExporterControlPanel controlPanel;

public HARExporter(ExportController exportController, Preferences preferences) {
super(exportController, preferences);
this.controlPanel = new HARExporterControlPanel(this);
}

@Override
public JComponent getExportPanel() {
return this.controlPanel;
}

public void exportEntries(List<LogEntry> entries) {
try {
File file = MoreHelp.getSaveFile("LoggerPlusPlus.har", "HAR Format", "har");
if (file.exists() && !MoreHelp.shouldOverwriteExistingFilePrompt())
return;

SwingWorkerWithProgressDialog<Void> importWorker = new SwingWorkerWithProgressDialog<Void>(
JOptionPane.getFrameForComponent(this.controlPanel), "HAR Export", "Exporting as HAR...",
entries.size()) {
@Override
protected Void doInBackground() throws Exception {
super.doInBackground();
try (FileWriter fileWriter = new FileWriter(file, false)) {
Type logEntryListType = new TypeToken<List<LogEntry>>(){}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(logEntryListType, new HarSerializer(String.valueOf(Globals.VERSION), "LoggerPlusPlus")).create();
gson.toJson(entries, logEntryListType, fileWriter);
}

return null;
}

@Override
protected void done() {
super.done();
JOptionPane.showMessageDialog(controlPanel, "Export as HAR completed.", "HAR Export",
JOptionPane.INFORMATION_MESSAGE);
}
};

importWorker.execute();

} catch (Exception e) {
// Cancelled.
}
}

@Override
public JMenuItem getExportEntriesMenuItem(List<LogEntry> entries) {
return new JMenuItem(new AbstractAction(
String.format("Export %d %s as HAR", entries.size(), entries.size() != 1 ? "entries" : "entry")) {
@Override
public void actionPerformed(ActionEvent e) {
exportEntries(entries);
}
});
}

public ExportController getExportController() {
return this.exportController;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.nccgroup.loggerplusplus.exports;

import com.coreyd97.BurpExtenderUtilities.Alignment;
import com.coreyd97.BurpExtenderUtilities.PanelBuilder;
import com.nccgroup.loggerplusplus.logentry.LogEntry;

import javax.swing.*;
import java.awt.*;
import java.util.List;

public class HARExporterControlPanel extends JPanel {
HARExporterControlPanel(HARExporter harExporter) {
this.setLayout(new BorderLayout());

JButton manualSaveButton = new JButton("Export as HAR");
manualSaveButton.addActionListener(actionEvent -> {
final List<LogEntry> entries = harExporter.getExportController().getLoggerPlusPlus().getLogEntries();
harExporter.exportEntries(entries);
});

this.add(PanelBuilder.build(new JComponent[][] { new JComponent[] { manualSaveButton }, },
new int[][] { new int[] { 1 }, }, Alignment.FILL, 1.0, 1.0), BorderLayout.CENTER);

this.setBorder(BorderFactory.createTitledBorder("HAR Exporter"));
}
}
204 changes: 204 additions & 0 deletions src/main/java/com/nccgroup/loggerplusplus/exports/HarSerializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package com.nccgroup.loggerplusplus.exports;

import java.io.IOException;
import java.net.HttpCookie;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.swing.Icon;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.nccgroup.loggerplusplus.LoggerPlusPlus;
import com.nccgroup.loggerplusplus.logentry.LogEntry;
import com.nccgroup.loggerplusplus.logentry.LogEntryField;

import org.apache.commons.lang3.StringUtils;

import burp.ICookie;
import burp.IParameter;
import burp.IRequestInfo;
import burp.IResponseInfo;

public class HarSerializer extends TypeAdapter<List<LogEntry>> {

private final String version;
private final String creator;

public HarSerializer(String version, String creator) {
this.version = version;
this.creator = creator;
}

@Override
public void write(JsonWriter writer, List<LogEntry> logEntries) throws IOException {
// Top level log object
writer.beginObject();
writer.name("log").beginObject();

writer.name("version").value("1.2");

// Creator object
writer.name("creator").beginObject();
writer.name("name").value(this.creator);
writer.name("version").value(this.version);
writer.endObject(); // end creator object

// Entries
writer.name("entries").beginArray();

for (LogEntry logEntry : logEntries) {
// Individual entry object
writer.beginObject();

final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
writer.name("startedDateTime").value(simpleDateFormat.format(logEntry.requestDateTime));

long time = logEntry.responseDateTime.getTime() - logEntry.requestDateTime.getTime();
writer.name("time").value(time);
writer.name("request").beginObject();
writer.name("method").value(logEntry.method);
writer.name("url").value(logEntry.url.toString());
writer.name("httpVersion").value(logEntry.requestHttpVersion);

writer.name("cookies").beginArray();
if (logEntry.hasCookieParam) {
List<IParameter> cookies = getRequestParametersByType(logEntry.requestResponse.getRequest(),
IParameter.PARAM_COOKIE);
for (IParameter cookie : cookies) {
writer.beginObject();
writer.name("name").value(cookie.getName());
writer.name("value").value(cookie.getValue());
writer.endObject();
}
}
writer.endArray(); // end request cookies array

writer.name("headers").beginArray();
for (String headerString : logEntry.requestHeaders) {
if (headerString.contains(":")) {
writer.beginObject();
String headerArray[] = headerString.split(":", 2);
writer.name("name").value(headerArray[0]);
writer.name("value").value(headerArray[1].trim());
writer.endObject();
}
}
writer.endArray(); // end request headers array

writer.name("queryString").beginArray();
if (logEntry.url.getQuery() != null) {
List<IParameter> queryParams = getRequestParametersByType(logEntry.requestResponse.getRequest(),
IParameter.PARAM_URL);
for (IParameter queryParam : queryParams) {
writer.beginObject();
writer.name("name").value(queryParam.getName());
writer.name("value").value(queryParam.getValue());
writer.endObject();
}
}
writer.endArray(); // end request queryString array

if (logEntry.hasBodyParam) {
writer.name("postData").beginObject();
writer.name("mimeType").value(logEntry.requestContentType);
List<IParameter> bodyParams = getRequestBodyParameters(logEntry.requestResponse.getRequest());
writer.name("params").beginArray();
for (IParameter bodyParam : bodyParams) {
writer.beginObject();
writer.name("name").value(bodyParam.getName());
writer.name("value").value(bodyParam.getValue());
writer.endObject();
}
writer.endArray(); // end params array
writer.name("text").value((String) logEntry.getValueByKey(LogEntryField.REQUEST_BODY));
writer.endObject(); // end postData object
}

writer.name("headersSize").value(logEntry.requestResponse.getRequest().length - logEntry.requestLength);
writer.name("bodySize").value(logEntry.requestLength);

writer.endObject(); // end request object

writer.name("response").beginObject();
writer.name("status").value(logEntry.responseStatus);
writer.name("statusText").value(logEntry.responseStatusText);
writer.name("httpVersion").value(logEntry.responseHttpVersion);

writer.name("cookies").beginArray();
if (logEntry.hasSetCookies) {
List<ICookie> cookies = getResponseCookies(logEntry.requestResponse.getResponse());

for (ICookie cookie : cookies) {
writer.beginObject();
writer.name("name").value(cookie.getName());
writer.name("value").value(cookie.getValue());
writer.name("path").value(cookie.getPath());
writer.name("domain").value(cookie.getDomain());
writer.endObject();
}
}
writer.endArray(); // end response cookies array

writer.name("headers").beginArray();
for (String headerString : logEntry.responseHeaders) {
if (headerString.contains(":")) {
writer.beginObject();
String headerArray[] = headerString.split(":", 2);
writer.name("name").value(headerArray[0]);
writer.name("value").value(headerArray[1].trim());
writer.endObject();
}
}
writer.endArray(); // end response headers array

writer.name("headersSize").value(logEntry.requestResponse.getResponse().length - logEntry.responseLength);
writer.name("bodySize").value(logEntry.responseLength);

writer.endObject(); // end response object

writer.endObject(); // end entry object
}

writer.endArray(); // end entries array

writer.endObject(); // end top level log object

writer.endObject(); // end top level object

}

private List<IParameter> getRequestParametersByType(byte[] request, byte paramType) {
IRequestInfo tempAnalyzedReq = LoggerPlusPlus.callbacks.getHelpers().analyzeRequest(request);
List<IParameter> params = tempAnalyzedReq.getParameters().stream()
.filter(iParameter -> iParameter.getType() == paramType).collect(Collectors.toList());
return params;
}

private List<IParameter> getRequestBodyParameters(byte[] request) {
IRequestInfo tempAnalyzedReq = LoggerPlusPlus.callbacks.getHelpers().analyzeRequest(request);
List<IParameter> params = tempAnalyzedReq.getParameters().stream()
.filter(iParameter -> iParameter.getType() != IParameter.PARAM_COOKIE
&& iParameter.getType() != IParameter.PARAM_URL)
.collect(Collectors.toList());
return params;
}

@Override
public List<LogEntry> read(JsonReader reader) throws IOException {
// TODO Implement HAR Import logic
return null;
}

private List<ICookie> getResponseCookies(byte[] responseMessage) {
IResponseInfo tempAnalyzedResp = LoggerPlusPlus.callbacks.getHelpers().analyzeResponse(responseMessage);

return tempAnalyzedResp.getCookies();
}

}
Loading