Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Commit 70bd6b5

Browse files
authored
Merge pull request #296 from nite23/blacklist_fix
Capture blacklisted requests in HAR
2 parents 10147c3 + 83d3bb1 commit 70bd6b5

File tree

5 files changed

+125
-28
lines changed

5 files changed

+125
-28
lines changed

browserup-proxy-core/src/main/java/com/browserup/bup/BrowserUpProxyServer.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,21 @@ public SSLEngine newSslEngine() {
465465
proxyServer = bootstrap.start();
466466

467467
addHarCaptureFilter();
468+
469+
addHttpFilterFactory(new HttpFiltersSourceAdapter() {
470+
@Override
471+
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
472+
return new BlacklistFilter(originalRequest, ctx, getBlacklist());
473+
}
474+
});
475+
476+
addHttpFilterFactory(new HttpFiltersSourceAdapter() {
477+
@Override
478+
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
479+
Whitelist currentWhitelist = whitelist.get();
480+
return new WhitelistFilter(originalRequest, ctx, isWhitelistEnabled(), currentWhitelist.getStatusCode(), currentWhitelist.getPatterns());
481+
}
482+
});
468483
}
469484

470485
@Override
@@ -1453,21 +1468,6 @@ public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerCont
14531468
}
14541469
});
14551470

1456-
addHttpFilterFactory(new HttpFiltersSourceAdapter() {
1457-
@Override
1458-
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
1459-
return new BlacklistFilter(originalRequest, ctx, getBlacklist());
1460-
}
1461-
});
1462-
1463-
addHttpFilterFactory(new HttpFiltersSourceAdapter() {
1464-
@Override
1465-
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
1466-
Whitelist currentWhitelist = whitelist.get();
1467-
return new WhitelistFilter(originalRequest, ctx, isWhitelistEnabled(), currentWhitelist.getStatusCode(), currentWhitelist.getPatterns());
1468-
}
1469-
});
1470-
14711471
addHttpFilterFactory(new HttpFiltersSourceAdapter() {
14721472
@Override
14731473
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {

browserup-proxy-core/src/main/java/com/browserup/bup/filters/BlacklistFilter.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
import io.netty.channel.ChannelHandlerContext;
88
import io.netty.handler.codec.http.DefaultFullHttpResponse;
9-
import io.netty.handler.codec.http.HttpHeaders;
109
import io.netty.handler.codec.http.HttpMethod;
1110
import io.netty.handler.codec.http.HttpObject;
1211
import io.netty.handler.codec.http.HttpRequest;
1312
import io.netty.handler.codec.http.HttpResponse;
1413
import io.netty.handler.codec.http.HttpResponseStatus;
1514
import com.browserup.bup.proxy.BlacklistEntry;
15+
import com.browserup.bup.util.HttpStatusClass;
16+
import io.netty.handler.codec.http.HttpUtil;
1617

1718
import java.util.Collection;
1819
import java.util.Collections;
@@ -22,6 +23,7 @@
2223
* that the blacklist at the time of construction will contain the same values when the filter is actually invoked, if the entries are modified concurrently.
2324
*/
2425
public class BlacklistFilter extends HttpsAwareFiltersAdapter {
26+
public static final String BLOCKED_PHRASE = "Request blocked";
2527
private final Collection<BlacklistEntry> blacklistedUrls;
2628

2729
public BlacklistFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, Collection<BlacklistEntry> blacklistedUrls) {
@@ -39,18 +41,23 @@ public HttpResponse clientToProxyRequest(HttpObject httpObject) {
3941
if (httpObject instanceof HttpRequest) {
4042
HttpRequest httpRequest = (HttpRequest) httpObject;
4143

42-
String url = getFullUrl(httpRequest);
44+
String url = getOriginalUrl();
4345

4446
for (BlacklistEntry entry : blacklistedUrls) {
45-
if (HttpMethod.CONNECT.equals(httpRequest.getMethod()) && entry.getHttpMethodPattern() == null) {
47+
if (HttpMethod.CONNECT.equals(httpRequest.method()) && entry.getHttpMethodPattern() == null) {
4648
// do not allow CONNECTs to be blacklisted unless a method pattern is explicitly specified
4749
continue;
4850
}
4951

50-
if (entry.matches(url, httpRequest.getMethod().name())) {
51-
HttpResponseStatus status = HttpResponseStatus.valueOf(entry.getStatusCode());
52-
HttpResponse resp = new DefaultFullHttpResponse(httpRequest.getProtocolVersion(), status);
53-
HttpHeaders.setContentLength(resp, 0L);
52+
if (entry.matches(url, httpRequest.method().name())) {
53+
HttpResponseStatus status;
54+
if(HttpStatusClass.UNKNOWN.equals(HttpStatusClass.valueOf(entry.getStatusCode()))) {
55+
status = new HttpResponseStatus(entry.getStatusCode(), BLOCKED_PHRASE);
56+
} else {
57+
status = HttpResponseStatus.valueOf(entry.getStatusCode());
58+
}
59+
HttpResponse resp = new DefaultFullHttpResponse(httpRequest.protocolVersion(), status);
60+
HttpUtil.setContentLength(resp, 0L);
5461

5562
return resp;
5663
}

browserup-proxy-core/src/main/java/com/browserup/bup/filters/HarCaptureFilter.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import java.util.EnumSet;
4848
import java.util.List;
4949
import java.util.Set;
50-
import java.util.concurrent.TimeUnit;
5150
import java.util.concurrent.atomic.AtomicInteger;
5251

5352
import static com.browserup.bup.util.BrowserUpProxyUtil.getTotalElapsedTime;
@@ -120,12 +119,14 @@ public class HarCaptureFilter extends HttpsAwareFiltersAdapter {
120119
*/
121120
private volatile HttpRequest capturedOriginalRequest;
122121

122+
private volatile boolean isResponse = false;
123+
123124
/**
124125
* True if this filter instance processed a {@link #proxyToServerResolutionSucceeded(String, java.net.InetSocketAddress)} call, indicating
125126
* that the hostname was resolved and populated in the HAR (if this is not a CONNECT).
126127
*/
127128
private volatile boolean addressResolved = false;
128-
129+
129130
/**
130131
* Create a new instance of the HarCaptureFilter that will capture request and response information. If no har is specified in the
131132
* constructor, this filter will do nothing.
@@ -255,6 +256,7 @@ public HttpResponse clientToProxyRequest(HttpObject httpObject) {
255256

256257
@Override
257258
public HttpObject serverToProxyResponse(HttpObject httpObject) {
259+
isResponse = true;
258260
// if a ServerResponseCaptureFilter is configured, delegate to it to collect the server's response. if it is not
259261
// configured, we still need to capture basic information (timings, HTTP status, etc.), just not content.
260262
if (responseCaptureFilter != null) {
@@ -286,8 +288,20 @@ public HttpObject serverToProxyResponse(HttpObject httpObject) {
286288
return super.serverToProxyResponse(httpObject);
287289
}
288290

291+
@Override
292+
public HttpObject proxyToClientResponse(HttpObject httpObject) {
293+
// if a subsequent filter short-circuited the response, capture it here
294+
if (!isResponse && httpObject instanceof HttpResponse) {
295+
HttpResponse httpResponse = (HttpResponse) httpObject;
296+
captureResponse(httpResponse);
297+
harEntry.setTime(getTotalElapsedTime(harEntry.getTimings()));
298+
}
299+
return super.proxyToClientResponse(httpObject);
300+
}
301+
289302
@Override
290303
public void serverToProxyResponseTimedOut() {
304+
isResponse = true;
291305
// replace any existing HarResponse that was created if the server sent a partial response
292306
HarResponse response = HarCaptureUtil.createHarResponseForFailure();
293307
harEntry.setResponse(response);
@@ -678,6 +692,7 @@ public InetSocketAddress proxyToServerResolutionStarted(String resolvingServerHo
678692

679693
@Override
680694
public void proxyToServerResolutionFailed(String hostAndPort) {
695+
isResponse = true;
681696
HarResponse response = HarCaptureUtil.createHarResponseForFailure();
682697
this.harEntry.setResponse(response);
683698

@@ -720,6 +735,7 @@ public void proxyToServerConnectionStarted() {
720735

721736
@Override
722737
public void proxyToServerConnectionFailed() {
738+
isResponse = true;
723739
HarResponse response = HarCaptureUtil.createHarResponseForFailure();
724740
this.harEntry.setResponse(response);
725741

@@ -733,6 +749,7 @@ public void proxyToServerConnectionFailed() {
733749

734750
@Override
735751
public void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx) {
752+
isResponse = true;
736753
long connectionSucceededTimeNanos = System.nanoTime();
737754

738755
// make sure the previous timestamp was captured, to avoid setting an absurd value in the har (see serverToProxyResponseReceiving())

browserup-proxy-core/src/main/java/com/browserup/bup/filters/WhitelistFilter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
package com.browserup.bup.filters;
66

7+
import com.browserup.bup.util.HttpStatusClass;
78
import io.netty.channel.ChannelHandlerContext;
89
import io.netty.handler.codec.http.DefaultFullHttpResponse;
9-
import io.netty.handler.codec.http.HttpHeaders;
1010
import io.netty.handler.codec.http.HttpObject;
1111
import io.netty.handler.codec.http.HttpRequest;
1212
import io.netty.handler.codec.http.HttpResponse;
@@ -15,7 +15,6 @@
1515
import org.littleshoot.proxy.impl.ProxyUtils;
1616

1717
import java.util.Collection;
18-
import java.util.Collections;
1918
import java.util.regex.Pattern;
2019

2120
import static java.util.Collections.*;
@@ -58,12 +57,17 @@ public HttpResponse clientToProxyRequest(HttpObject httpObject) {
5857

5958
boolean urlWhitelisted;
6059

61-
String url = getFullUrl(httpRequest);
60+
String url = getOriginalUrl();
6261

6362
urlWhitelisted = whitelistUrls.stream().anyMatch(pattern -> pattern.matcher(url).matches());
6463

6564
if (!urlWhitelisted) {
66-
HttpResponseStatus status = HttpResponseStatus.valueOf(whitelistResponseCode);
65+
HttpResponseStatus status;
66+
if(HttpStatusClass.UNKNOWN.equals(HttpStatusClass.valueOf(whitelistResponseCode))) {
67+
status = new HttpResponseStatus(whitelistResponseCode, BlacklistFilter.BLOCKED_PHRASE);
68+
} else {
69+
status = HttpResponseStatus.valueOf(whitelistResponseCode);
70+
}
6771
HttpResponse resp = new DefaultFullHttpResponse(httpRequest.protocolVersion(), status);
6872
HttpUtil.setContentLength(resp, 0L);
6973

browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/NewHarTest.groovy

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.junit.After
2626
import org.junit.Test
2727
import org.mockito.invocation.InvocationOnMock
2828
import org.mockito.stubbing.Answer
29+
import static org.hamcrest.Matchers.isEmptyOrNullString
2930

3031
import java.text.SimpleDateFormat
3132
import java.util.concurrent.TimeUnit
@@ -916,6 +917,74 @@ class NewHarTest extends MockServerTest {
916917
assertTrue(har.log.entries[0].time > 0)
917918
}
918919

920+
@Test
921+
void testHttpsBlacklistedUrlInHar() {
922+
def stubUrl = "/httpsblacklistedurl"
923+
stubFor(get(urlEqualTo(stubUrl))
924+
.willReturn(ok())
925+
)
926+
proxy = new BrowserUpProxyServer()
927+
proxy.blacklistRequests(".*", 405)
928+
proxy.setTrustAllServers(true)
929+
proxy.start()
930+
931+
proxy.newHar()
932+
933+
String requestUrl = "https://localhost:${mockServerHttpsPort}/httpsblacklistedurl"
934+
935+
NewProxyServerTestUtil.getNewHttpClient(proxy.port).withCloseable {
936+
CloseableHttpResponse response = it.execute(new HttpGet(requestUrl))
937+
assertEquals("Did not receive blacklisted status code in response", 405, response.getStatusLine().getStatusCode())
938+
939+
String responseBody = NewProxyServerTestUtil.toStringAndClose(response.getEntity().getContent())
940+
assertThat("Expected blacklisted response to contain 0-length body", responseBody, isEmptyOrNullString())
941+
}
942+
943+
Thread.sleep(500)
944+
Har har = proxy.getHar()
945+
946+
assertThat("Expected to find entries in the HAR", har.getLog().getEntries(), not(empty()))
947+
948+
HarResponse harResponse = har.log.entries[0].response
949+
assertNotNull("No HAR response found", harResponse)
950+
assertEquals("Expected blacklisted status code for the request", 405, harResponse.status)
951+
assertEquals("Expected default value for bodySize for response timeout", -1L, harResponse.bodySize)
952+
}
953+
954+
@Test
955+
void testHttpsWhitelistedUrlInHar() {
956+
def stubUrl = "/httpsblacklistedurl"
957+
stubFor(get(urlEqualTo(stubUrl))
958+
.willReturn(ok())
959+
)
960+
proxy = new BrowserUpProxyServer()
961+
proxy.whitelistRequests(null, 405)
962+
proxy.setTrustAllServers(true)
963+
proxy.start()
964+
965+
proxy.newHar()
966+
967+
String requestUrl = "https://localhost:${mockServerHttpsPort}/httpsblacklistedurl"
968+
969+
NewProxyServerTestUtil.getNewHttpClient(proxy.port).withCloseable {
970+
CloseableHttpResponse response = it.execute(new HttpGet(requestUrl))
971+
assertEquals("Did not receive blacklisted status code in response", 405, response.getStatusLine().getStatusCode())
972+
973+
String responseBody = NewProxyServerTestUtil.toStringAndClose(response.getEntity().getContent())
974+
assertThat("Expected blacklisted response to contain 0-length body", responseBody, isEmptyOrNullString())
975+
}
976+
977+
Thread.sleep(500)
978+
Har har = proxy.getHar()
979+
980+
assertThat("Expected to find entries in the HAR", har.getLog().getEntries(), not(empty()))
981+
982+
HarResponse harResponse = har.log.entries[0].response
983+
assertNotNull("No HAR response found", harResponse)
984+
assertEquals("Expected blacklisted status code for the request", 405, harResponse.status)
985+
assertEquals("Expected default value for bodySize for response timeout", -1L, harResponse.bodySize)
986+
}
987+
919988
@Test
920989
void testRedirectUrlCapturedForRedirects() {
921990
def stubUrl = "/test300"

0 commit comments

Comments
 (0)