Skip to content

Commit

Permalink
Merge pull request #5836 from thc202/exim/refactor-har
Browse files Browse the repository at this point in the history
exim: move HAR entry conversion to utils
  • Loading branch information
psiinon authored Oct 22, 2024
2 parents 574c0bc + a6cfb22 commit 9f6590d
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,14 @@

import de.sstoehr.harreader.HarReader;
import de.sstoehr.harreader.HarReaderException;
import de.sstoehr.harreader.model.HarContent;
import de.sstoehr.harreader.model.HarEntry;
import de.sstoehr.harreader.model.HarHeader;
import de.sstoehr.harreader.model.HarLog;
import de.sstoehr.harreader.model.HarResponse;
import de.sstoehr.harreader.model.HarTiming;
import java.io.File;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
Expand All @@ -47,7 +39,6 @@
import org.parosproxy.paros.network.HttpHeader;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpResponseHeader;
import org.zaproxy.addon.commonlib.ui.ProgressPaneListener;
import org.zaproxy.addon.exim.ExtensionExim;
import org.zaproxy.zap.utils.Stats;
Expand Down Expand Up @@ -210,80 +201,14 @@ protected static List<HttpMessage> getHttpMessages(HarLog log)

private static HttpMessage getHttpMessage(HarEntry harEntry)
throws HttpMalformedHeaderException {
HttpMessage result;
try {
result = HarUtils.createHttpMessage(harEntry.getRequest());
return HarUtils.createHttpMessage(harEntry);
} catch (HttpMalformedHeaderException headerEx) {
LOGGER.warn(
"Failed to create HTTP Request Header for HAR entry.\n{}",
"Failed to create HTTP Request/Response Header for HAR entry.\n{}",
headerEx.getMessage());
return null;
}
result.setTimeSentMillis(
Optional.ofNullable(harEntry.getStartedDateTime()).map(Date::getTime).orElse(0L));
result.setTimeElapsedMillis(
Optional.ofNullable(harEntry.getTimings()).map(HarTiming::getReceive).orElse(0));
setHttpResponse(harEntry.getResponse(), result);
return result;
}

private static void setHttpResponse(HarResponse harResponse, HttpMessage message)
throws HttpMalformedHeaderException {
StringBuilder strBuilderResHeader = new StringBuilder();

// empty responses without status code are possible
if (harResponse.getStatus() == 0) {
return;
}

strBuilderResHeader
.append(harResponse.getHttpVersion())
.append(' ')
.append(harResponse.getStatus())
.append(' ')
.append(harResponse.getStatusText())
.append(HttpHeader.CRLF);

for (HarHeader harHeader : harResponse.getHeaders()) {
String value = harHeader.getValue();
if (value.contains("\n") || value.contains("\r")) {
LOGGER.info(
"{}\n\t{} value contains CR or LF and is likely invalid (though it may have been successfully set to the message):\n\t{}",
message.getRequestHeader().getURI(),
harHeader.getName(),
StringEscapeUtils.escapeJava(value));
}
strBuilderResHeader
.append(harHeader.getName())
.append(": ")
.append(harHeader.getValue())
.append(HttpHeader.CRLF);
}
strBuilderResHeader.append(HttpHeader.CRLF);

HarContent harContent = harResponse.getContent();
try {
message.setResponseHeader(new HttpResponseHeader(strBuilderResHeader.toString()));
} catch (HttpMalformedHeaderException he) {
LOGGER.info(
"Couldn't set response header for: {}", message.getRequestHeader().getURI());
}
message.setResponseFromTargetHost(true);
if (harContent != null) {
if ("base64".equals(harContent.getEncoding())) {
var text = harContent.getText();
if (text != null)
try {
message.setResponseBody(Base64.getDecoder().decode(text));
} catch (IllegalArgumentException e) {
LOGGER.debug(
"Failed to base64 decode body {}. Setting as plain text.", text, e);
message.setResponseBody(text);
}
} else {
message.setResponseBody(harContent.getText());
}
}
}

private static boolean containsIgnoreCase(List<String> checkList, String candidate) {
Expand Down
95 changes: 93 additions & 2 deletions addOns/exim/src/main/java/org/zaproxy/addon/exim/har/HarUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
Expand All @@ -66,6 +68,8 @@
* @see HttpMessage
*/
public final class HarUtils {
private static final String BASE64_BODY_ENCODING = "base64";

/**
* The prefix for custom HAR fields produced by ZAP.
*
Expand Down Expand Up @@ -171,6 +175,93 @@ public static HttpMessage createHttpMessage(HarRequest harRequest)
new HttpRequestBody(strBuilderReqBody.toString()));
}

/**
* Creates an {@code HttpMessage} from the given HAR entry.
*
* @param harEntry the HAR entry.
* @return the HTTP message containing the request and response from the HAR entry.
* @throws HttpMalformedHeaderException if an error occurred while creating the request or
* response header from the HAR entry.
* @since 0.13.0
*/
public static HttpMessage createHttpMessage(HarEntry harEntry)
throws HttpMalformedHeaderException {
HttpMessage message = createHttpMessage(harEntry.getRequest());

message.setTimeSentMillis(
Optional.ofNullable(harEntry.getStartedDateTime()).map(Date::getTime).orElse(0L));
message.setTimeElapsedMillis(
Optional.ofNullable(harEntry.getTimings()).map(HarTiming::getReceive).orElse(0));

setHttpResponse(harEntry.getResponse(), message);

return message;
}

private static void setHttpResponse(HarResponse harResponse, HttpMessage message)
throws HttpMalformedHeaderException {
// empty responses without status code are possible
if (harResponse.getStatus() == 0) {
return;
}

StringBuilder strBuilderResHeader =
new StringBuilder()
.append(harResponse.getHttpVersion())
.append(' ')
.append(harResponse.getStatus())
.append(' ')
.append(harResponse.getStatusText())
.append(HttpHeader.CRLF);

boolean mixedNewlineChars = false;
for (HarHeader harHeader : harResponse.getHeaders()) {
String value = harHeader.getValue();
if (value.contains("\n") || value.contains("\r")) {
mixedNewlineChars = true;
LOGGER.info(
"{}\n\t{} value contains CR or LF and is likely invalid (though it may have been successfully set to the message):\n\t{}",
message.getRequestHeader().getURI(),
harHeader.getName(),
StringEscapeUtils.escapeJava(value));
}
strBuilderResHeader
.append(harHeader.getName())
.append(": ")
.append(harHeader.getValue())
.append(HttpHeader.CRLF);
}
strBuilderResHeader.append(HttpHeader.CRLF);

try {
message.setResponseHeader(strBuilderResHeader.toString());
} catch (HttpMalformedHeaderException e) {
if (!mixedNewlineChars) {
throw e;
}
LOGGER.info(
"Couldn't set response header for: {}", message.getRequestHeader().getURI());
}
message.setResponseFromTargetHost(true);

HarContent harContent = harResponse.getContent();
if (harContent != null) {
if (BASE64_BODY_ENCODING.equals(harContent.getEncoding())) {
var text = harContent.getText();
if (text != null)
try {
message.setResponseBody(Base64.getDecoder().decode(text));
} catch (IllegalArgumentException e) {
LOGGER.debug(
"Failed to base64 decode body {}. Setting as plain text.", text, e);
message.setResponseBody(text);
}
} else {
message.setResponseBody(harContent.getText());
}
}
}

/**
* Creates a {@code HarEntry} from the given message.
*
Expand Down Expand Up @@ -321,7 +412,7 @@ public static HarResponse createHarResponse(HttpMessage httpMessage) {
if (contentType == null || contentType.isEmpty()) {
contentType = "";
if (httpMessage.getResponseBody().length() != 0) {
encoding = "base64";
encoding = BASE64_BODY_ENCODING;
text = Base64.getEncoder().encodeToString(httpMessage.getResponseBody().getBytes());
}
} else {
Expand All @@ -332,7 +423,7 @@ public static HarResponse createHarResponse(HttpMessage httpMessage) {
}

if (!lcContentType.startsWith("text")) {
encoding = "base64";
encoding = BASE64_BODY_ENCODING;
text = Base64.getEncoder().encodeToString(httpMessage.getResponseBody().getBytes());
} else {
text = httpMessage.getResponseBody().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,68 @@ void shouldCreateHttpMessageFromHarRequest() throws Exception {
assertThat(message.getRequestBody().toString(), is(equalTo("{\"a\":\"1\", \"b\":2}")));
}

@Test
void shouldCreateHttpMessageFromHarEntry() throws Exception {
// Given
String entryValue =
"{\n"
+ " \"startedDateTime\": \"1970-01-01T00:00:00.001+00:00\",\n"
+ " \"request\": {\n"
+ " \"method\": \"GET\",\n"
+ " \"url\": \"http://www.example.org/\",\n"
+ " \"httpVersion\": \"HTTP/1.1\",\n"
+ " \"headers\": [\n"
+ " {\n"
+ " \"name\": \"host\",\n"
+ " \"value\": \"www.example.org\"\n"
+ " }\n"
+ " ],\n"
+ " \"postData\": {\n"
+ " \"mimeType\": \"\",\n"
+ " \"params\": [],\n"
+ " \"text\": \"\"\n"
+ " }\n"
+ " },\n"
+ " \"response\": {\n"
+ " \"status\": 500,\n"
+ " \"statusText\": \"Internal Server Error\",\n"
+ " \"httpVersion\": \"HTTP/1.1\",\n"
+ " \"cookies\": [],\n"
+ " \"headers\": [\n"
+ " {\n"
+ " \"name\": \"Content-Type\",\n"
+ " \"value\": \"text/html; charset=UTF-8\"\n"
+ " }\n"
+ " ],\n"
+ " \"content\": {\n"
+ " \"mimeType\": \"text/html; charset=UTF-8\",\n"
+ " \"text\": \"Response Body\"\n"
+ " }\n"
+ " },\n"
+ " \"timings\": {\n"
+ " \"receive\": 123\n"
+ " }\n"
+ "}";
HarEntry entry = HarUtils.JSON_MAPPER.readValue(entryValue, HarEntry.class);
// When
HttpMessage message = HarUtils.createHttpMessage(entry);
// Then
assertThat(
message.getRequestHeader().toString(),
is(
equalTo(
"GET http://www.example.org/ HTTP/1.1\r\nhost: www.example.org\r\n\r\n")));
assertThat(message.getRequestBody().toString(), is(equalTo("")));
assertThat(
message.getResponseHeader().toString(),
is(
equalTo(
"HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n")));
assertThat(message.getResponseBody().toString(), is(equalTo("Response Body")));
assertThat(message.getTimeSentMillis(), is(equalTo(1L)));
assertThat(message.getTimeElapsedMillis(), is(equalTo(123)));
}

@Test
void shouldCreateHarEntryWithMessageNote() throws Exception {
// Given
Expand Down

0 comments on commit 9f6590d

Please sign in to comment.