diff --git a/.gitignore b/.gitignore index 3b9e72b..43936ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .gradle -.idea/* +.idea out build __pycache__ diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 15a15b2..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 3163861..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index bc8d0a3..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 9b94467..b9b1b3d 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ For transparently rewriting HTTP/HTTPS responses. The mitmproxy plugin lets ever ## Pre-requisites -* [`mitmproxy` V4](https://mitmproxy.org/) must be installed and runnable from the terminal. The install method cannot be a prebuilt binary or homebrew, since those packages are missing the Python websockets module. Install via `pip` or from source. -* Python 3.6, since we use the new async/await syntax in the mitmproxy plugin +* [`mitmproxy` V9](https://mitmproxy.org/) must be installed and runnable from the terminal. The install method cannot be a prebuilt binary or homebrew, since those packages are missing the Python websockets module. Install via `pip` or from source. +* Python 3.6 and above, since we use the new async/await syntax in the mitmproxy plugin * `pip3 install websockets` Maven: @@ -35,13 +35,13 @@ Maven: io.appium mitmproxy-java - 1.6.2 + 2.0.2 ``` Gradle: ``` -testCompile group: 'io.appium', name: 'mitmproxy-java', version: '1.6.1' +testCompile group: 'io.appium', name: 'mitmproxy-java', version: '2.0.2' ``` ## Usage @@ -49,22 +49,34 @@ testCompile group: 'io.appium', name: 'mitmproxy-java', version: '1.6.1' ```java List messages = new ArrayList(); +//optional, default port is 8080 +int mitmproxyPort = 8090; + +//optional, you can pass null if no extra params +List extraMitmproxyParams = Arrays.asList("param1", "value1", "param2", "value2"); + // remember to set local OS proxy settings in the Network Preferences proxy = new MitmproxyJava("/usr/local/bin/mitmdump", (InterceptedMessage m) -> { - System.out.println("intercepted request for " + m.requestURL.toString()); + System.out.println("intercepted request for " + m.getRequest().getUrl()); messages.add(m); return m; -}); +}, mitmproxyPort, extraMitmproxyParams); proxy.start(); // do stuff proxy.stop(); +``` +If the above code doesn't work, and you are getting +`Error=2, No such file or directory` please re-check your mitmdump path in new MitmproxyJava initialization. +You can get mitmdump path using below command: +```shell +whereis mitmdump ``` -See AppriumPro article for more guidelines: https://appiumpro.com/editions/65 +See AppiumPro article for more guidelines: https://appiumpro.com/editions/65 Example can be found here: https://github.com/cloudgrey-io/appiumpro/blob/master/java/src/test/java/Edition065_Capture_Network_Requests.java ## Your Java code is bad and you should feel bad @@ -75,3 +87,7 @@ I'm no Java expert! You may see some bad patterns, like my terrible disregard fo See [node-mitmproxy](https://github.com/jvilk/mitmproxy-node/blob/master/README.md#your-python-plugin-is-bad-and-you-should-feel-bad). Pull requests welcome! +## Develop + +Upload to Central Repository with command `./gradlew uploadArchives` +Set username and password in `build.gradle` file \ No newline at end of file diff --git a/build.gradle b/build.gradle index 605a4a0..6b779fb 100644 --- a/build.gradle +++ b/build.gradle @@ -5,14 +5,13 @@ plugins { } group = 'io.appium' -version = '1.6.2' +version = '2.0.2' archivesBaseName = 'mitmproxy-java' sourceCompatibility = 1.8 repositories { mavenCentral() - jcenter() } dependencies { @@ -24,7 +23,14 @@ dependencies { implementation group: 'org.zeroturnaround', name: 'zt-process-killer', version: '1.9' implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26' testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' testCompile group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9' + testCompile group: 'org.assertj', name: 'assertj-core', version: '3.12.2' + + compileOnly 'org.projectlombok:lombok:1.18.8' + annotationProcessor 'org.projectlombok:lombok:1.18.8' + + compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.0' } task javadocJar(type: Jar) { @@ -39,6 +45,10 @@ artifacts { archives javadocJar, sourcesJar } signing { + setRequired { + // condition for when signing is required + gradle.taskGraph.hasTask("uploadArchives") + } sign configurations.archives } @@ -53,10 +63,10 @@ uploadArchives { // Destination repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: ossrhUsername, password: ossrhPassword) + authentication(userName: 'username', password: 'password') } snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: ossrhUsername, password: ossrhPassword) + authentication(userName: 'username', password: 'password') } // Add required metadata to POM @@ -91,4 +101,4 @@ uploadArchives { } } } -} \ No newline at end of file +} diff --git a/src/main/java/io/appium/mitmproxy/InterceptedMessage.java b/src/main/java/io/appium/mitmproxy/InterceptedMessage.java index 47326b2..18f347f 100644 --- a/src/main/java/io/appium/mitmproxy/InterceptedMessage.java +++ b/src/main/java/io/appium/mitmproxy/InterceptedMessage.java @@ -1,107 +1,42 @@ package io.appium.mitmproxy; -import java.io.IOException; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Data; -public class InterceptedMessage { - - public byte[] requestBody; - public byte[] responseBody; +import java.util.List; - public String requestMethod; - public URL requestURL; - public List requestHeaders; - public int responseCode; - public List responseHeaders; +@Data +public class InterceptedMessage { + private final static ObjectMapper objectMapper = new ObjectMapper(); - public InterceptedMessage(ByteBuffer buffer) throws IOException { - buffer = buffer.order(ByteOrder.LITTLE_ENDIAN); + private Request request; - int metadataSize = buffer.getInt(); - int request_content_size = buffer.getInt(); - int response_content_size = buffer.getInt(); + private Response response; - byte[] metadataBytes = new byte[metadataSize]; - buffer.get(metadataBytes); + @Data + public static class Request { - requestBody = new byte[request_content_size]; - buffer.get(requestBody); + private String method; - responseBody = new byte[response_content_size]; - buffer.get(responseBody); + private String url; - ObjectMapper mapper = new ObjectMapper(); - JsonNode metadata = mapper.readTree(metadataBytes); - requestMethod = metadata.get("request").get("method").asText(); - requestURL = new URL(metadata.get("request").get("url").asText()); - JsonNode headers = metadata.get("request").get("headers"); - requestHeaders = new ArrayList<>(); - for (JsonNode headerNode : headers) { - String[] headerArray = new String[2]; - headerArray[0] = headerNode.get(0).asText(); - headerArray[1] = headerNode.get(1).asText(); - requestHeaders.add(headerArray); - } + private List headers; - responseCode = metadata.get("response").get("status_code").asInt(); - headers = metadata.get("request").get("headers"); - responseHeaders = new ArrayList<>(); - for (JsonNode headerNode : headers) { - String[] headerArray = new String[2]; - headerArray[0] = headerNode.get(0).asText(); - headerArray[1] = headerNode.get(1).asText(); - responseHeaders.add(headerArray); - } + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private byte[] body; } - public ByteBuffer serializedResponseToMitmproxy() throws JsonProcessingException { - int contentLength = responseBody.length; - - // create JSON for metadata. Which is the responseCode and responseHeaders. - // while we're at it, set the Content-Length header - ObjectMapper mapper = new ObjectMapper(); - ObjectNode metadataRoot = mapper.createObjectNode(); - metadataRoot.put("status_code", responseCode); - - ArrayNode headersNode = mapper.createArrayNode(); - List headerNodes = responseHeaders.stream().map((h) -> { - ArrayNode headerPair = mapper.createArrayNode(); - headerPair.add(h[0]); - if (h[0].equals("content-length")) { - headerPair.add(Integer.toString(contentLength)); - } else { - headerPair.add(h[1]); - } - return headerPair; - }).collect(Collectors.toList()); - headersNode.addAll(headerNodes); - metadataRoot.set("headers", headersNode); - - String metadataJson = mapper.writeValueAsString(metadataRoot); - byte[] metadata = metadataJson.getBytes(StandardCharsets.UTF_8); - int metadataLength = metadata.length; + @Data + public static class Response { + @JsonProperty("status_code") + private int statusCode; - ByteBuffer buffer = ByteBuffer.allocate(8 + metadataLength + contentLength); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(metadataLength); - buffer.putInt(contentLength); - buffer.put(metadata); - buffer.put(responseBody); + private List headers; - return (ByteBuffer) buffer.rewind(); + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private byte[] body; } } \ No newline at end of file diff --git a/src/main/java/io/appium/mitmproxy/MessageSerializer.java b/src/main/java/io/appium/mitmproxy/MessageSerializer.java new file mode 100644 index 0000000..2429c65 --- /dev/null +++ b/src/main/java/io/appium/mitmproxy/MessageSerializer.java @@ -0,0 +1,58 @@ +package io.appium.mitmproxy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; + +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class MessageSerializer { + + private final static ObjectMapper objectMapper = new ObjectMapper(); + private static final int START_BYTES = 8; + + @SneakyThrows(IOException.class) + public InterceptedMessage deserializeMessage(ByteBuffer buffer) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + + int metadataSize = buffer.getInt(); + int request_content_size = buffer.getInt(); + int response_content_size = buffer.getInt(); + + byte[] metadataBytes = new byte[metadataSize]; + buffer.get(metadataBytes); + + byte[] requestBody = new byte[request_content_size]; + buffer.get(requestBody); + + byte[] responseBody = new byte[response_content_size]; + buffer.get(responseBody); + + InterceptedMessage interceptedMessage = objectMapper.readValue(metadataBytes, InterceptedMessage.class); + interceptedMessage.getRequest().setBody(requestBody); + interceptedMessage.getResponse().setBody(responseBody); + + return interceptedMessage; + } + + public ByteBuffer serializeMessage(InterceptedMessage message) throws JsonProcessingException { + byte[] responseBody = message.getResponse().getBody(); + + int contentLength = responseBody.length; + + byte[] metadata = objectMapper.writeValueAsBytes(message.getResponse()); + int metadataLength = metadata.length; + + ByteBuffer buffer = ByteBuffer.allocate(START_BYTES + metadataLength + contentLength); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(metadataLength); + buffer.putInt(contentLength); + buffer.put(metadata); + buffer.put(message.getResponse().getBody()); + + return (ByteBuffer) buffer.rewind(); + } +} diff --git a/src/main/java/io/appium/mitmproxy/MitmproxyJava.java b/src/main/java/io/appium/mitmproxy/MitmproxyJava.java index 61a7897..2da256a 100644 --- a/src/main/java/io/appium/mitmproxy/MitmproxyJava.java +++ b/src/main/java/io/appium/mitmproxy/MitmproxyJava.java @@ -1,5 +1,8 @@ package io.appium.mitmproxy; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.ProcessResult; import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; @@ -10,103 +13,113 @@ import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.URISyntaxException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.function.Function; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + +@Slf4j public class MitmproxyJava { - private String mitmproxyPath; + private static final String LOCALHOST_IP = "127.0.0.1"; + private static final int WEBSOCKET_PORT = 8765; + private static final int TIMEOUT_FOR_SOCKET_CHECKING_MINS = 5; + + private final String mitmproxyPath; + + private final Function messageInterceptor; + + private final int proxyPort; + private MitmproxyServer server; + + private final List extraMitmdumpParams; + private Future mitmproxyProcess; - public static final int WEBSOCKET_PORT = 8765; - public MitmproxyJava(String mitmproxyPath, Function messageInterceptor) { + public MitmproxyJava(String mitmproxyPath, Function messageInterceptor, int proxyPort, List extraMitmdumpParams) { this.mitmproxyPath = mitmproxyPath; - server = new MitmproxyServer(new InetSocketAddress("localhost", WEBSOCKET_PORT), messageInterceptor); - server.start(); + this.messageInterceptor = messageInterceptor; + this.proxyPort = proxyPort; + this.extraMitmdumpParams = extraMitmdumpParams; } - public void start() throws IOException, TimeoutException, URISyntaxException { - System.out.println("starting mitmproxy on port 8080"); + public MitmproxyJava(String mitmproxyPath, Function messageInterceptor) { + this(mitmproxyPath, messageInterceptor, 8080, null); + } + + public MitmproxyJava start() throws IOException, TimeoutException { + log.info("Starting mitmproxy on port {}", proxyPort); + + server = new MitmproxyServer(new InetSocketAddress(LOCALHOST_IP, WEBSOCKET_PORT), messageInterceptor); + server.start(); // python script file is zipped inside our jar. extract it into a temporary file. String pythonScriptPath = extractPythonScriptToFile(); + final List mitmproxyStartParams = new ArrayList<>(); + mitmproxyStartParams.add(mitmproxyPath); + mitmproxyStartParams.add("--anticache"); + mitmproxyStartParams.add("-p"); + mitmproxyStartParams.add(String.valueOf(proxyPort)); + mitmproxyStartParams.add("-s"); + mitmproxyStartParams.add(pythonScriptPath); + + // adding params if needed for mitmproxy + if (isNotEmpty(this.extraMitmdumpParams)) { + mitmproxyStartParams.addAll(this.extraMitmdumpParams); + } + mitmproxyProcess = new ProcessExecutor() - .command(mitmproxyPath, "--anticache", "-s", pythonScriptPath) + .command(mitmproxyStartParams) .redirectOutput(Slf4jStream.ofCaller().asInfo()) .destroyOnExit() .start() .getFuture(); - waitForPortToBeInUse(8080); - System.out.println("mitmproxy started on port 8080"); - + waitForPortToBeInUse(proxyPort); + log.info("Mitmproxy started on port {}", proxyPort); + return this; } - private String extractPythonScriptToFile() throws URISyntaxException, IOException { + private String extractPythonScriptToFile() throws IOException { File outfile = File.createTempFile("mitmproxy-python-plugin", ".py"); - InputStream instream = getClass().getClassLoader().getResourceAsStream("scripts/proxy.py"); - FileOutputStream outstream = new FileOutputStream(outfile); - - byte[] buffer = new byte[1024]; + try ( + InputStream inputStream = getClass().getClassLoader().getResourceAsStream("scripts/proxy.py"); + FileOutputStream outputStream = new FileOutputStream(outfile)) { - int length; - /*copying the contents from input stream to - * output stream using read and write methods - */ - while ((length = instream.read(buffer)) > 0){ - outstream.write(buffer, 0, length); + IOUtils.copy(inputStream, outputStream); } - //Closing the input/output file streams - instream.close(); - outstream.close(); - return outfile.getCanonicalPath(); } - public void stop() throws IOException, InterruptedException { + public void stop() throws InterruptedException { if (mitmproxyProcess != null) { mitmproxyProcess.cancel(true); } server.stop(1000); - Thread.sleep(200); // this pains me. but it seems that it takes a moment for the server to actually relinquish the port it uses. + waitForPortToBeFree(proxyPort); } - private void waitForPortToBeInUse(int port) throws TimeoutException { - boolean inUse = false; - Socket s = null; - int tries = 0; - int maxTries = 60 * 1000 / 100; + @SneakyThrows + private void waitForPortToBeFree(int port) { + final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); - while (!inUse) { - try - { - s = new Socket("localhost", port); - } - catch (IOException e) - { - inUse = false; + while (true) { + if(timeoutTime.isBefore(LocalDateTime.now())){ + throw new TimeoutException("Timed out waiting for mitmproxy to stop"); } - finally - { - if(s != null) { - inUse = true; - try { - s.close(); - } catch (Exception e) { - } - break; - } - } - tries++; - if (tries == maxTries) { - throw new TimeoutException("Timed out waiting for mitmproxy to start"); + try (Socket s = new Socket(LOCALHOST_IP, port)) { + } catch (IOException ioe) { + return; } + try { Thread.sleep(100); } catch (InterruptedException e) { @@ -114,5 +127,26 @@ private void waitForPortToBeInUse(int port) throws TimeoutException { } } } -} + @SneakyThrows + private void waitForPortToBeInUse(int port) { + final LocalDateTime timeoutTime = LocalDateTime.now().plusMinutes(TIMEOUT_FOR_SOCKET_CHECKING_MINS); + + while (true) { + + if(timeoutTime.isBefore(LocalDateTime.now())){ + throw new TimeoutException("Timed out waiting for mitmproxy to start"); + } + + try (Socket s = new Socket(LOCALHOST_IP, port)) { + return; + } catch (IOException ioe) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/io/appium/mitmproxy/MitmproxyServer.java b/src/main/java/io/appium/mitmproxy/MitmproxyServer.java index 309af12..c88e433 100644 --- a/src/main/java/io/appium/mitmproxy/MitmproxyServer.java +++ b/src/main/java/io/appium/mitmproxy/MitmproxyServer.java @@ -4,71 +4,70 @@ import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.function.Function; public class MitmproxyServer extends WebSocketServer { - private Function interceptor; + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); - public MitmproxyServer(InetSocketAddress address, Function interceptor) { + private final Function interceptor; + + private final MessageSerializer messageSerializer; + + MitmproxyServer(InetSocketAddress address, Function interceptor) { super(address); this.interceptor = interceptor; + this.messageSerializer = new MessageSerializer(); } @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { - System.out.println("new connection to websocket server" + conn.getRemoteSocketAddress()); + LOGGER.debug("new connection to websocket server" + conn.getRemoteSocketAddress()); } @Override public void onClose(WebSocket conn, int code, String reason, boolean remote) { - System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); + LOGGER.debug("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason); } @Override public void onMessage(WebSocket conn, String message) { - System.out.println("received message from " + conn.getRemoteSocketAddress() + ": " + message); + LOGGER.debug("received message from " + conn.getRemoteSocketAddress() + " : " + message); } @Override - public void onMessage( WebSocket conn, ByteBuffer message ) { - InterceptedMessage intercepted = null; - InterceptedMessage modifiedMessage = null; - - try { - intercepted = new InterceptedMessage(message); - } catch (IOException e) { - System.out.println("Could not parse message"); - e.printStackTrace(); - } + public void onMessage(WebSocket conn, ByteBuffer rawInputMessage) { + InterceptedMessage incomingMessage = this.messageSerializer.deserializeMessage(rawInputMessage); - modifiedMessage = interceptor.apply(intercepted); + InterceptedMessage modifiedMessage = interceptor.apply(incomingMessage); // if the supplied interceptor function does not return a message, assume no changes were intended and just // complete the request + InterceptedMessage messageToSendBack = modifiedMessage; + if (modifiedMessage == null) { - modifiedMessage = intercepted; + messageToSendBack = incomingMessage; } try { - conn.send(modifiedMessage.serializedResponseToMitmproxy()); + conn.send(this.messageSerializer.serializeMessage(messageToSendBack)); } catch (JsonProcessingException e) { - System.out.println("Could not encode response to mitmproxy"); - e.printStackTrace(); + LOGGER.error(e.getMessage()); } } @Override public void onError(WebSocket conn, Exception ex) { - System.err.println("an error occured on connection " + conn.getRemoteSocketAddress() + ":" + ex); + LOGGER.error("an error occured on connection " + conn.getRemoteSocketAddress() + ":" + ex); } @Override public void onStart() { - System.out.println("websocket server started successfully"); + LOGGER.info("websocket server started successfully"); } } diff --git a/src/main/resources/scripts/proxy.py b/src/main/resources/scripts/proxy.py index f108b1f..01ab562 100644 --- a/src/main/resources/scripts/proxy.py +++ b/src/main/resources/scripts/proxy.py @@ -150,7 +150,7 @@ def request(self, flow): new_metadata = message_response[0] new_body = message_response[1] - flow.response = http.HTTPResponse.make( + flow.response = http.Response.make( new_metadata['status_code'], new_body, map(convert_headers_to_bytes, new_metadata['headers']) @@ -198,7 +198,7 @@ def response(self, flow): #print("Prepping response!") - flow.response = http.HTTPResponse.make( + flow.response = http.Response.make( new_metadata['status_code'], new_body, map(convert_headers_to_bytes, new_metadata['headers']) diff --git a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java index f893cfc..85ea9c3 100644 --- a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java +++ b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java @@ -7,93 +7,128 @@ import org.junit.Test; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import static junit.framework.TestCase.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; public class MitmproxyJavaTest { + //private static final String MITMDUMP_PATH = "C:\\Python37\\Scripts\\mitmdump.exe"; + private static final String MITMDUMP_PATH = getMITMDumpPath(); + + static String getMITMDumpPath(){ + if(System.getProperty("os.name").contains("Mac")){ + return "/opt/homebrew/bin/mitmdump"; + } + return "/usr/local/bin/mitmdump"; + } @Test - public void ConstructorTest() throws URISyntaxException, IOException, InterruptedException { - MitmproxyJava proxy = new MitmproxyJava("/usr/local/bin/mitmdump", (InterceptedMessage m) -> { - System.out.println(m.requestURL.toString()); + public void ConstructorTest() throws InterruptedException, IOException, TimeoutException { + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + System.out.println(m.getRequest().getUrl()); return m; }); + proxy.start(); System.out.println("advanced in test"); proxy.stop(); } @Test - public void SimpleTest() throws InterruptedException, ExecutionException, TimeoutException, IOException, URISyntaxException, UnirestException { - List messages = new ArrayList(); + public void SimpleTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); - MitmproxyJava proxy = new MitmproxyJava("/usr/local/bin/mitmdump", (InterceptedMessage m) -> { + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { messages.add(m); return m; }); proxy.start(); Unirest.setProxy(new HttpHost("localhost", 8080)); - Unirest.get("http://appium.io").asString(); + Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); proxy.stop(); + final InterceptedMessage firstMessage = messages.get(0); - assertTrue(messages.size() > 0); - - InterceptedMessage appiumIORequest = messages.stream().filter((m) -> m.requestURL.getHost().equals("appium.io")).findFirst().get(); - - assertTrue(appiumIORequest.responseCode == 200); + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); } @Test - public void NullInterceptorReturnTest() throws InterruptedException, ExecutionException, TimeoutException, IOException, URISyntaxException, UnirestException { - List messages = new ArrayList(); + public void NullInterceptorReturnTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); - MitmproxyJava proxy = new MitmproxyJava("/usr/local/bin/mitmdump", (InterceptedMessage m) -> { + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { messages.add(m); return null; - }); + }, 8087, null); proxy.start(); - Unirest.setProxy(new HttpHost("localhost", 8080)); - Unirest.get("http://appium.io").asString(); + Unirest.setProxy(new HttpHost("localhost", 8087)); + Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); proxy.stop(); - assertTrue(messages.size() > 0); + assertThat(messages).isNotEmpty(); - InterceptedMessage appiumIORequest = messages.stream().filter((m) -> m.requestURL.getHost().equals("appium.io")).findFirst().get(); + final InterceptedMessage firstMessage = messages.get(0); - assertTrue(appiumIORequest.responseCode == 200); + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200); } @Test - public void ResponseModificationTest() throws InterruptedException, ExecutionException, TimeoutException, IOException, URISyntaxException, UnirestException { - List messages = new ArrayList(); + public void ResponseModificationTest() throws InterruptedException, TimeoutException, IOException, UnirestException { + List messages = new ArrayList<>(); - MitmproxyJava proxy = new MitmproxyJava("/usr/local/bin/mitmdump", (InterceptedMessage m) -> { + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { messages.add(m); - m.responseBody = "Hi from Test".getBytes(StandardCharsets.UTF_8); + m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); + m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + m.getResponse().setStatusCode(208); return m; }); proxy.start(); Unirest.setProxy(new HttpHost("localhost", 8080)); - HttpResponse response = Unirest.get("http://appium.io").asString(); + HttpResponse response = Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString(); + proxy.stop(); - assertTrue(response.getBody().equals("Hi from Test")); + assertThat(response.getBody()).isEqualTo("Hi from Test"); - proxy.stop(); + final InterceptedMessage firstMessage = messages.get(0); - assertTrue(messages.size() > 0); + assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io"); + assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"}); + assertThat(firstMessage.getResponse().getHeaders()).containsOnlyOnce(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(208); + } + + @Test + public void shouldAddParametersToMitmdumpStart() throws IOException, TimeoutException, InterruptedException { + List mitmdumpParams = new ArrayList<>(); + mitmdumpParams.add("testParam"); + + List spiedParams = spy(mitmdumpParams); + + MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> { + m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8)); + m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"}); + m.getResponse().setStatusCode(208); + return m; + }, 8087, spiedParams); + + proxy.start(); + proxy.stop(); - InterceptedMessage appiumIORequest = messages.stream().filter((m) -> m.requestURL.getHost().equals("appium.io")).findFirst().get(); + //to verify that additional params were actually included to start path + verify(spiedParams).toArray(); - assertTrue(appiumIORequest.responseCode == 200); } } \ No newline at end of file