com.browserup.bup.proxy.bricks.ProxyResource Maven / Gradle / Ivy
The newest version!
package com.browserup.bup.proxy.bricks;
import com.browserup.bup.MitmProxyServer;
import com.browserup.bup.exception.ProxyExistsException;
import com.browserup.bup.exception.ProxyPortsExhaustedException;
import com.browserup.bup.exception.UnsupportedCharsetException;
import com.browserup.bup.filters.JavascriptRequestResponseFilter;
import com.browserup.bup.mitmproxy.MitmProxyProcessManager.MitmProxyLoggingLevel;
import com.browserup.bup.proxy.CaptureType;
import com.browserup.bup.proxy.MitmProxyManager;
import com.browserup.bup.proxy.auth.AuthType;
import com.browserup.bup.util.BrowserUpHttpUtil;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.sitebricks.At;
import com.google.sitebricks.client.transport.Json;
import com.google.sitebricks.client.transport.Text;
import com.google.sitebricks.headless.Reply;
import com.google.sitebricks.headless.Request;
import com.google.sitebricks.headless.Service;
import com.google.sitebricks.http.Delete;
import com.google.sitebricks.http.Get;
import com.google.sitebricks.http.Post;
import com.google.sitebricks.http.Put;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.ScriptException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static java.util.stream.Collectors.toList;
import de.sstoehr.harreader.model.Har;
@At("/proxy")
@Service
public class ProxyResource {
private static final Logger LOG = LoggerFactory.getLogger(ProxyResource.class);
private final MitmProxyManager proxyManager;
@Inject
public ProxyResource(MitmProxyManager proxyManager) {
this.proxyManager = proxyManager;
}
@Get
public Reply> getProxies() {
LOG.info("GET /");
Collection proxyList = proxyManager.get().stream()
.map(proxy -> new ProxyDescriptor(proxy.getPort()))
.collect(toList());
return Reply.with(new ProxyListDescriptor(proxyList)).as(Json.class);
}
@Post
public Reply> newProxy(Request request) {
LOG.info("POST /");
LOG.info(request.params().toString());
String systemProxyHost = System.getProperty("http.proxyHost");
String systemProxyPort = System.getProperty("http.proxyPort");
String systemNonProxyHosts = System.getProperty("http.nonProxyHosts");
String httpProxy = request.param("httpProxy");
String httpNonProxyHosts = request.param("httpNonProxyHosts");
String proxyUsername = request.param("proxyUsername");
String proxyPassword = request.param("proxyPassword");
boolean upstreamProxyHttps = "true".equals(request.param("proxyHTTPS"));
Hashtable options = new Hashtable<>();
// If the upstream proxy is specified via query params that should override any default system level proxy.
String upstreamHttpProxy = null;
if (httpProxy != null) {
upstreamHttpProxy = httpProxy;
} else if ((systemProxyHost != null) && (systemProxyPort != null)) {
upstreamHttpProxy = String.format("%s:%s", systemProxyHost, systemProxyPort);
}
// If the upstream proxy is defined, we should add the non proxy hosts (proxy exceptions) as well.
List upstreamNonProxyHosts = null;
if (upstreamHttpProxy != null) {
// override system non proxy hosts with the provided ones
if (httpNonProxyHosts != null) {
upstreamNonProxyHosts = Arrays.asList(httpNonProxyHosts.split("\\|"));
} else if (systemNonProxyHosts != null) {
upstreamNonProxyHosts = Arrays.asList(systemNonProxyHosts.split("\\|"));
}
}
String paramBindAddr = request.param("bindAddress");
String paramServerBindAddr = request.param("serverBindAddress");
Integer paramPort = request.param("port") == null ? null : Integer.parseInt(request.param("port"));
String useEccString = request.param("useEcc");
boolean useEcc = Boolean.parseBoolean(useEccString);
String loggingLevel = request.param("mitmProxyLoggingLevel");
MitmProxyLoggingLevel level = MitmProxyLoggingLevel.info;
if (StringUtils.isNotEmpty(loggingLevel)) {
level = MitmProxyLoggingLevel.valueOf(loggingLevel);
}
String trustAllServersString = request.param("trustAllServers");
boolean trustAllServers = Boolean.parseBoolean(trustAllServersString);
LOG.debug("POST proxy instance on bindAddress `{}` & port `{}` & serverBindAddress `{}`",
paramBindAddr, paramPort, paramServerBindAddr);
MitmProxyServer proxy;
try {
proxy = proxyManager.create(upstreamHttpProxy, upstreamProxyHttps, upstreamNonProxyHosts, proxyUsername, proxyPassword, paramPort, paramBindAddr, paramServerBindAddr, useEcc, trustAllServers, level);
} catch (ProxyExistsException ex) {
return Reply.with(new ProxyDescriptor(ex.getPort())).status(455).as(Json.class);
} catch (ProxyPortsExhaustedException ex) {
return Reply.saying().status(456);
} catch (Exception ex) {
StringWriter s = new StringWriter();
ex.printStackTrace(new PrintWriter(s));
return Reply.with(s).as(Text.class).status(550);
}
return Reply.with(new ProxyDescriptor(proxy.getPort())).as(Json.class);
}
@Get
@At("/:port/har")
public Reply> getHar(@Named("port") int port, Request request) {
LOG.info("GET /{}/har", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
boolean cleanHar = "true".equals(request.param("cleanHar"));
Har har = proxy.getHar(cleanHar);
return Reply.with(har).as(Json.class);
}
@Put
@At("/:port/har")
public Reply> newHar(@Named("port") int port, Request request) {
LOG.info("PUT /{}/har", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String initialPageRef = request.param("initialPageRef");
String initialPageTitle = request.param("initialPageTitle");
Har oldHar = proxy.newHar(initialPageRef, initialPageTitle);
String captureHeaders = request.param("captureHeaders");
String captureContent = request.param("captureContent");
String captureBinaryContent = request.param("captureBinaryContent");
Set captureTypes = new HashSet<>();
if (Boolean.parseBoolean(captureHeaders)) {
captureTypes.addAll(CaptureType.getHeaderCaptureTypes());
}
if (Boolean.parseBoolean(captureContent)) {
captureTypes.addAll(CaptureType.getAllContentCaptureTypes());
}
if (Boolean.parseBoolean(captureBinaryContent)) {
captureTypes.addAll(CaptureType.getBinaryContentCaptureTypes());
}
proxy.setHarCaptureTypes(captureTypes);
String captureCookies = request.param("captureCookies");
if (Boolean.parseBoolean(captureCookies)) {
proxy.enableHarCaptureTypes(CaptureType.getCookieCaptureTypes());
}
if (oldHar != null) {
return Reply.with(oldHar).as(Json.class);
} else {
return Reply.saying().noContent();
}
}
@Put
@At("/:port/har/pageRef")
public Reply> setPage(@Named("port") int port, Request request) {
LOG.info("PUT /{}/har/pageRef", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String pageRef = request.param("pageRef");
String pageTitle = request.param("pageTitle");
proxy.newPage(pageRef, pageTitle);
return Reply.saying().ok();
}
@Post
@At("/:port/har/commands/endPage")
public Reply> endPage(@Named("port") int port, Request request) {
LOG.info("POST /{}/commands/endPage", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.endPage();
return Reply.saying().ok();
}
@Post
@At("/:port/har/commands/endHar")
public Reply> endHar(@Named("port") int port, Request request) {
LOG.info("POST /{}/commands/endHar", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.endHar();
return Reply.saying().ok();
}
@Get
@At("/:port/blocklist")
public Reply> getBlocklist(@Named("port") int port, Request request) {
LOG.info("GET /{}/blocklist", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
return Reply.with(proxy.getBlocklist()).as(Json.class);
}
@Put
@At("/:port/blocklist")
public Reply> blocklist(@Named("port") int port, Request request) {
LOG.info("PUT /{}/blocklist", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String blocklist = request.param("regex");
int responseCode = parseResponseCode(request.param("status"));
String method = request.param("method");
proxy.blocklistRequests(blocklist, responseCode, method);
return Reply.saying().ok();
}
@Delete
@At("/:port/blocklist")
public Reply> clearBlocklist(@Named("port") int port, Request request) {
LOG.info("DELETE /{}/blocklist", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.clearBlocklist();
return Reply.saying().ok();
}
@Get
@At("/:port/allowlist")
public Reply> getAllowlist(@Named("port") int port, Request request) {
LOG.info("GET /{}/allowlist", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
return Reply.with(proxy.getAllowlistUrls()).as(Json.class);
}
@Put
@At("/:port/allowlist")
public Reply> allowlist(@Named("port") int port, Request request) {
LOG.info("PUT /{}/allowlist", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String regex = request.param("regex");
int responseCode = parseResponseCode(request.param("status"));
proxy.allowlistRequests(Arrays.asList(regex.split(",")), responseCode);
return Reply.saying().ok();
}
@Delete
@At("/:port/allowlist")
public Reply> clearAllowlist(@Named("port") int port, Request request) {
LOG.info("DELETE /{}/allowlist", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.disableAllowlist();
return Reply.saying().ok();
}
@Post
@At("/:port/auth/basic/:domain")
public Reply> autoBasicAuth(@Named("port") int port, @Named("domain") String domain, Request request) {
LOG.info("POST /{}/auth/basic/{}", port, domain);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
Map credentials = request.read(HashMap.class).as(Json.class);
proxy.autoAuthorization(domain, credentials.get("username"), credentials.get("password"), AuthType.BASIC);
return Reply.saying().ok();
}
@Post
@At("/:port/headers")
public Reply> updateHeaders(@Named("port") int port, Request request) {
LOG.info("POST /{}/headers", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
Map headers = request.read(Map.class).as(Json.class);
headers.forEach(proxy::addHeader);
return Reply.saying().ok();
}
@Post
@At("/:port/filter/request")
public Reply> addRequestFilter(@Named("port") int port, Request request) throws IOException, ScriptException {
LOG.info("POST /{}/filter/request", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
JavascriptRequestResponseFilter requestResponseFilter = new JavascriptRequestResponseFilter();
String script = getEntityBodyFromRequest(request);
requestResponseFilter.setRequestFilterScript(script);
proxy.addRequestFilter(requestResponseFilter);
return Reply.saying().ok();
}
@Post
@At("/:port/filter/response")
public Reply> addResponseFilter(@Named("port") int port, Request request) throws IOException, ScriptException {
LOG.info("POST /{}/filter/response", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
JavascriptRequestResponseFilter requestResponseFilter = new JavascriptRequestResponseFilter();
String script = getEntityBodyFromRequest(request);
requestResponseFilter.setResponseFilterScript(script);
proxy.addResponseFilter(requestResponseFilter);
return Reply.saying().ok();
}
@Put
@At("/:port/limit")
public Reply> limit(@Named("port") int port, Request request) {
LOG.info("PUT /{}/limit", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String upstreamKbps = request.param("upstreamKbps");
if (upstreamKbps != null) {
try {
long upstreamBytesPerSecond = Long.parseLong(upstreamKbps) * 1024;
proxy.setWriteBandwidthLimit(upstreamBytesPerSecond);
} catch (NumberFormatException e) {
LOG.warn("Invalid upstreamKbps value");
return Reply.saying().badRequest();
}
}
String upstreamBps = request.param("upstreamBps");
if (upstreamBps != null) {
try {
proxy.setWriteBandwidthLimit(Integer.parseInt(upstreamBps));
} catch (NumberFormatException e) {
LOG.warn("Invalid upstreamBps value");
return Reply.saying().badRequest();
}
}
String downstreamKbps = request.param("downstreamKbps");
if (downstreamKbps != null) {
try {
long downstreamBytesPerSecond = Long.parseLong(downstreamKbps) * 1024;
proxy.setReadBandwidthLimit(downstreamBytesPerSecond);
} catch (NumberFormatException e) {
LOG.warn("Invalid downstreamKbps value");
return Reply.saying().badRequest();
}
}
String downstreamBps = request.param("downstreamBps");
if (downstreamBps != null) {
try {
proxy.setReadBandwidthLimit(Integer.parseInt(downstreamBps));
} catch (NumberFormatException e) {
LOG.warn("Invalid downstreamBps value");
return Reply.saying().badRequest();
}
}
String latency = request.param("latency");
if (latency != null) {
try {
proxy.setLatency(Long.parseLong(latency), TimeUnit.MILLISECONDS);
} catch (NumberFormatException e) {
LOG.warn("Invalid latency value");
return Reply.saying().badRequest();
}
}
if (request.param("upstreamMaxKB") != null) {
LOG.warn("upstreamMaxKB no longer supported");
return Reply.saying().badRequest();
}
if (request.param("downstreamMaxKB") != null) {
LOG.warn("downstreamMaxKB no longer supported");
return Reply.saying().badRequest();
}
if (request.param("payloadPercentage") != null) {
LOG.warn("payloadPercentage no longer supported");
return Reply.saying().badRequest();
}
if (request.param("maxBitsPerSecond") != null) {
LOG.warn("maxBitsPerSecond no longer supported");
return Reply.saying().badRequest();
}
if (request.param("enable") != null) {
LOG.warn("enable no longer supported. Limits, if set, will always be enabled.");
return Reply.saying().badRequest();
}
return Reply.saying().ok();
}
@Put
@At("/:port/timeout")
public Reply> timeout(@Named("port") int port, Request request) {
LOG.info("PUT /{}/timeout", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String requestTimeout = request.param("requestTimeout");
if (requestTimeout != null) {
try {
proxy.setRequestTimeout(Integer.parseInt(requestTimeout), TimeUnit.MILLISECONDS);
} catch (NumberFormatException e) {
LOG.warn("Invalid requestTimeout value");
return Reply.saying().badRequest();
}
}
String readTimeout = request.param("readTimeout");
if (readTimeout != null) {
try {
proxy.setIdleConnectionTimeout(Integer.parseInt(readTimeout), TimeUnit.MILLISECONDS);
} catch (NumberFormatException e) {
LOG.warn("Invalid readTimeout value");
return Reply.saying().badRequest();
}
}
String connectionTimeout = request.param("connectionTimeout");
if (connectionTimeout != null) {
try {
proxy.setConnectTimeout(Integer.parseInt(connectionTimeout), TimeUnit.MILLISECONDS);
} catch (NumberFormatException e) {
LOG.warn("Invalid connectionTimeout value");
return Reply.saying().badRequest();
}
}
String dnsCacheTimeout = request.param("dnsCacheTimeout");
if (dnsCacheTimeout != null) {
try {
proxy.getHostNameResolver().setPositiveDNSCacheTimeout(Integer.parseInt(dnsCacheTimeout), TimeUnit.SECONDS);
proxy.getHostNameResolver().setNegativeDNSCacheTimeout(Integer.parseInt(dnsCacheTimeout), TimeUnit.SECONDS);
} catch (NumberFormatException e) {
LOG.warn("Invalid dnsCacheTimeout value");
return Reply.saying().badRequest();
}
}
return Reply.saying().ok();
}
@Delete
@At("/:port")
public Reply> delete(@Named("port") int port) {
LOG.info("DELETE /{}", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxyManager.delete(port);
return Reply.saying().ok();
}
@Post
@At("/:port/hosts")
public Reply> remapHosts(@Named("port") int port, Request request) {
LOG.info("POST /{}/hosts", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
@SuppressWarnings("unchecked") Map headers = request.read(Map.class).as(Json.class);
headers.forEach((key, value) -> {
proxy.getHostNameResolver().remapHost(key, value);
proxy.getHostNameResolver().setNegativeDNSCacheTimeout(0, TimeUnit.SECONDS);
proxy.getHostNameResolver().setPositiveDNSCacheTimeout(0, TimeUnit.SECONDS);
proxy.getHostNameResolver().clearDNSCache();
});
return Reply.saying().ok();
}
@Put
@At("/:port/wait")
public Reply> wait(@Named("port") int port, Request request) {
LOG.info("PUT /{}/wait", port);
LOG.info(request.params().toString());
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String quietPeriodInMs = request.param("quietPeriodInMs");
String timeoutInMs = request.param("timeoutInMs");
proxy.waitForQuiescence(Long.parseLong(quietPeriodInMs), Long.parseLong(timeoutInMs), TimeUnit.MILLISECONDS);
return Reply.saying().ok();
}
@Delete
@At("/:port/dns/cache")
public Reply> clearDnsCache(@Named("port") int port) {
LOG.info("DELETE /{}/dns/cache", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.getHostNameResolver().clearDNSCache();
return Reply.saying().ok();
}
@Put
@At("/:port/rewrite")
public Reply> rewriteUrl(@Named("port") int port, Request request) {
LOG.info("PUT /{}/rewrite", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
String match = request.param("matchRegex");
String replace = request.param("replace");
proxy.rewriteUrl(match, replace);
return Reply.saying().ok();
}
@Delete
@At("/:port/rewrite")
public Reply> clearRewriteRules(@Named("port") int port, Request request) {
LOG.info("DELETE /{}/rewrite", port);
MitmProxyServer proxy = proxyManager.get(port);
if (proxy == null) {
return Reply.saying().notFound();
}
proxy.clearRewriteRules();
return Reply.saying().ok();
}
@Put
@At("/:port/retry")
public Reply> retryCount(@Named("port") int port, Request request) {
LOG.warn("/port/retry API is no longer supported");
return Reply.saying().badRequest();
}
private int parseResponseCode(String response) {
int responseCode = 200;
if (response != null) {
try {
responseCode = Integer.parseInt(response);
} catch (NumberFormatException e) {
}
}
return responseCode;
}
public static class ProxyDescriptor {
private int port;
public ProxyDescriptor() {
}
public ProxyDescriptor(int port) {
this.port = port;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
public static class ProxyListDescriptor {
private Collection proxyList;
public ProxyListDescriptor() {
}
public ProxyListDescriptor(Collection proxyList) {
this.proxyList = proxyList;
}
public Collection getProxyList() {
return proxyList;
}
public void setProxyList(Collection proxyList) {
this.proxyList = proxyList;
}
}
private String getEntityBodyFromRequest(Request request) throws IOException {
String contentTypeHeader = request.header("Content-Type");
Charset charset;
try {
charset = BrowserUpHttpUtil.readCharsetInContentTypeHeader(contentTypeHeader);
} catch (UnsupportedCharsetException e) {
java.nio.charset.UnsupportedCharsetException cause = e.getUnsupportedCharsetExceptionCause();
LOG.error("Character set declared in Content-Type header is not supported. Content-Type header: {}", contentTypeHeader, cause);
throw cause;
}
if (charset == null) {
charset = BrowserUpHttpUtil.DEFAULT_HTTP_CHARSET;
}
ByteArrayOutputStream entityBodyBytes = new ByteArrayOutputStream();
request.readTo(entityBodyBytes);
return new String(entityBodyBytes.toByteArray(), charset);
}
private Optional getAssertionTimeFromRequest(Request request) {
String timeParam = request.param("milliseconds");
if (StringUtils.isEmpty(timeParam)) {
LOG.warn("Time parameter not present");
return Optional.empty();
}
Long time;
try {
time = Long.valueOf(timeParam);
} catch (Exception ex) {
LOG.warn("Time parameter not valid", ex);
return Optional.empty();
}
return Optional.of(time);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy