Skip to content

Commit 03c8a62

Browse files
fixes + tests
1 parent 0caa81d commit 03c8a62

12 files changed

Lines changed: 767 additions & 98 deletions

src/main/java/com/testingbot/tunnel/Api.java

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,56 @@ public class Api {
4242
private final ObjectMapper objectMapper = new ObjectMapper();
4343
private Supplier<HttpClientBuilder> httpClientBuilderSupplier = HttpClientBuilder::create;
4444

45+
// Connect timeout = how long to wait for TCP/TLS to come up.
46+
// Response timeout = how long to wait once the request is sent.
47+
static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.of(5, TimeUnit.SECONDS);
48+
static final Timeout DEFAULT_RESPONSE_TIMEOUT = Timeout.of(30, TimeUnit.SECONDS);
49+
50+
private Timeout connectTimeout = DEFAULT_CONNECT_TIMEOUT;
51+
private Timeout responseTimeout = DEFAULT_RESPONSE_TIMEOUT;
52+
4553
public Api(App app) {
4654
this.app = app;
4755
this.clientKey = app.getClientKey();
4856
this.clientSecret = app.getClientSecret();
4957
}
5058

59+
/** For testing: shrink the HTTP timeouts so tests don't have to wait 30s. */
60+
void setTimeoutsForTesting(Timeout connect, Timeout response) {
61+
this.connectTimeout = connect;
62+
this.responseTimeout = response;
63+
}
64+
65+
private RequestConfig defaultRequestConfig() {
66+
return RequestConfig.custom()
67+
.setConnectTimeout(connectTimeout)
68+
.setConnectionRequestTimeout(connectTimeout)
69+
.setResponseTimeout(responseTimeout)
70+
.build();
71+
}
72+
73+
private HttpClientBuilder newBuilderWithProxy() {
74+
HttpClientBuilder builder = httpClientBuilderSupplier.get();
75+
builder.setDefaultRequestConfig(defaultRequestConfig());
76+
if (app.getProxy() != null) {
77+
String[] splitted = app.getProxy().split(":", 2);
78+
int port = splitted.length > 1 ? Integer.parseInt(splitted[1]) : 80;
79+
if (app.getProxyAuth() != null) {
80+
String[] credentials = app.getProxyAuth().split(":", 2);
81+
if (credentials.length == 2) {
82+
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
83+
credsProvider.setCredentials(
84+
new AuthScope(splitted[0], port),
85+
new UsernamePasswordCredentials(credentials[0], credentials[1].toCharArray())
86+
);
87+
builder.setDefaultCredentialsProvider(credsProvider);
88+
}
89+
}
90+
builder.setProxy(new HttpHost("http", splitted[0], port));
91+
}
92+
return builder;
93+
}
94+
5195
/**
5296
* For testing purposes only - allows overriding the API host
5397
*/
@@ -104,30 +148,7 @@ public JsonNode pollTunnel(String tunnelID) throws Exception {
104148
}
105149

106150
public void destroyTunnel() throws Exception {
107-
RequestConfig requestConfig = RequestConfig.custom()
108-
.setConnectTimeout(Timeout.of(1, TimeUnit.SECONDS))
109-
.setConnectionRequestTimeout(Timeout.of(1, TimeUnit.SECONDS))
110-
.build();
111-
112-
HttpClientBuilder builder = httpClientBuilderSupplier.get();
113-
builder.setDefaultRequestConfig(requestConfig);
114-
115-
if (app.getProxy() != null) {
116-
String[] splitted = app.getProxy().split(":");
117-
int port = splitted.length > 1 ? Integer.parseInt(splitted[1]) : 80;
118-
if (app.getProxyAuth() != null) {
119-
String[] credentials = app.getProxyAuth().split(":");
120-
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
121-
credsProvider.setCredentials(
122-
new AuthScope(splitted[0], port),
123-
new UsernamePasswordCredentials(credentials[0], credentials[1].toCharArray())
124-
);
125-
builder.setDefaultCredentialsProvider(credsProvider);
126-
}
127-
128-
HttpHost proxy = new HttpHost("http", splitted[0], port);
129-
builder.setProxy(proxy);
130-
}
151+
HttpClientBuilder builder = newBuilderWithProxy();
131152

132153
try (CloseableHttpClient httpClient = builder.build()) {
133154
String auth = this.clientKey + ":" + this.clientSecret;
@@ -143,24 +164,7 @@ public void destroyTunnel() throws Exception {
143164

144165
private JsonNode _post(String url, List<NameValuePair> postData) throws Exception {
145166
try {
146-
HttpClientBuilder builder = httpClientBuilderSupplier.get();
147-
148-
if (app.getProxy() != null) {
149-
String[] splitted = app.getProxy().split(":");
150-
int port = splitted.length > 1 ? Integer.parseInt(splitted[1]) : 80;
151-
if (app.getProxyAuth() != null) {
152-
String[] credentials = app.getProxyAuth().split(":");
153-
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
154-
credsProvider.setCredentials(
155-
new AuthScope(splitted[0], port),
156-
new UsernamePasswordCredentials(credentials[0], credentials[1].toCharArray())
157-
);
158-
builder.setDefaultCredentialsProvider(credsProvider);
159-
}
160-
161-
HttpHost proxy = new HttpHost("http", splitted[0], port);
162-
builder.setProxy(proxy);
163-
}
167+
HttpClientBuilder builder = newBuilderWithProxy();
164168

165169
String responseBody;
166170
try (CloseableHttpClient httpClient = builder.build()) {
@@ -195,23 +199,7 @@ private JsonNode _post(String url, List<NameValuePair> postData) throws Exceptio
195199

196200
private JsonNode _get(String url) throws Exception {
197201
try {
198-
HttpClientBuilder builder = httpClientBuilderSupplier.get();
199-
if (app.getProxy() != null) {
200-
String[] splitted = app.getProxy().split(":");
201-
int port = splitted.length > 1 ? Integer.parseInt(splitted[1]) : 80;
202-
if (app.getProxyAuth() != null) {
203-
String[] credentials = app.getProxyAuth().split(":");
204-
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
205-
credsProvider.setCredentials(
206-
new AuthScope(splitted[0], port),
207-
new UsernamePasswordCredentials(credentials[0], credentials[1].toCharArray())
208-
);
209-
builder.setDefaultCredentialsProvider(credsProvider);
210-
}
211-
212-
HttpHost proxy = new HttpHost("http", splitted[0], port);
213-
builder.setProxy(proxy);
214-
}
202+
HttpClientBuilder builder = newBuilderWithProxy();
215203

216204
String responseBody;
217205
try (CloseableHttpClient httpClient = builder.build()) {

src/main/java/com/testingbot/tunnel/App.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ static int getMajorJavaVersion() {
9292
return Runtime.version().feature();
9393
}
9494

95+
// RFC 7230 token: 1*tchar
96+
private static final java.util.regex.Pattern HEADER_NAME = java.util.regex.Pattern.compile("[!#$%&'*+\\-.^_`|~0-9A-Za-z]+");
97+
98+
static void validateHeader(String name, String value) throws ParseException {
99+
if (name == null || name.isEmpty() || !HEADER_NAME.matcher(name).matches()) {
100+
throw new ParseException("Invalid header name in --extra-headers: '" + name + "' (must be an RFC 7230 token)");
101+
}
102+
if (value == null) {
103+
throw new ParseException("Header value for '" + name + "' is null");
104+
}
105+
if (value.indexOf('\r') >= 0 || value.indexOf('\n') >= 0) {
106+
throw new ParseException("Header value for '" + name + "' must not contain CR or LF");
107+
}
108+
}
109+
95110
public static void main(String... args) throws Exception {
96111
if (!checkJavaVersion()) {
97112
System.exit(1);
@@ -282,6 +297,7 @@ public static void main(String... args) throws Exception {
282297
while (keyIterator.hasNext()) {
283298
String key = keyIterator.next();
284299
String value = obj.get(key).asText();
300+
validateHeader(key, value);
285301
app.addCustomHeader(key, value);
286302
}
287303
}
@@ -356,7 +372,7 @@ public static void main(String... args) throws Exception {
356372
app.boot();
357373
} catch (ParseException parseException) {
358374
System.err.println(parseException.getMessage());
359-
System.exit(0);
375+
System.exit(2);
360376
}
361377
}
362378
private PidPoller pidPoller;

src/main/java/com/testingbot/tunnel/HttpProxy.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,20 @@ public HttpProxy(App app) {
6161
httpProxy.setStopAtShutdown(true);
6262

6363
ConnectHandler connectHandler = new CustomConnectHandler(app);
64+
((CustomConnectHandler) connectHandler).setBlackList(app.getFastFail());
6465
WebsocketHandler websocketHandler = new WebsocketHandler();
6566

6667
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
6768
contextHandler.setContextPath("/"); // Root path for all requests
69+
contextHandler.setAttribute("extra_headers", app.getCustomHeaders());
6870

6971
// AsyncProxyServlet for proxying HTTP requests
7072
ServletHolder proxyServlet = new ServletHolder(new TunnelProxyServlet());
7173
proxyServlet.setInitParameter("idleTimeout", "120000");
7274
proxyServlet.setInitParameter("timeout", "120000");
7375

7476
if (app.getFastFail() != null && app.getFastFail().length > 0) {
75-
StringBuilder sb = new StringBuilder();
76-
for (String domain : app.getFastFail()) {
77-
if (!domain.contains(":")) {
78-
domain = domain + ":80"; // default port 80
79-
}
80-
sb.append(domain).append(",");
81-
}
82-
proxyServlet.setInitParameter("blackList", sb.toString());
77+
proxyServlet.setInitParameter("blackList", String.join(",", app.getFastFail()));
8378
}
8479

8580
if (app.isDebugMode()) {

0 commit comments

Comments
 (0)