Skip to content

Commit bd7a184

Browse files
8263442: Potential bug in jdk.internal.net.http.common.Utils.CONTEXT_RESTRICTED
Reviewed-by: dfuchs
1 parent 2335362 commit bd7a184

File tree

6 files changed

+208
-16
lines changed

6 files changed

+208
-16
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import jdk.internal.net.http.common.MinimalFuture;
3434
import jdk.internal.net.http.common.SSLTube;
3535
import jdk.internal.net.http.common.Utils;
36+
import static jdk.internal.net.http.common.Utils.ProxyHeaders;
3637

3738
/**
3839
* An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
@@ -47,7 +48,7 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
4748
HttpClientImpl client,
4849
String[] alpn,
4950
InetSocketAddress proxy,
50-
HttpHeaders proxyHeaders)
51+
ProxyHeaders proxyHeaders)
5152
{
5253
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn);
5354
this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders);

src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import jdk.internal.net.http.common.Log;
5353
import jdk.internal.net.http.common.Utils;
5454
import static java.net.http.HttpClient.Version.HTTP_2;
55+
import static jdk.internal.net.http.common.Utils.ProxyHeaders;
5556

5657
/**
5758
* Wraps socket channel layer and takes care of SSL also.
@@ -351,14 +352,10 @@ BiPredicate<String,String> contextRestricted(HttpRequestImpl request, HttpClient
351352
// Composes a new immutable HttpHeaders that combines the
352353
// user and system header but only keeps those headers that
353354
// start with "proxy-"
354-
private static HttpHeaders proxyTunnelHeaders(HttpRequestImpl request) {
355-
Map<String, List<String>> combined = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
356-
combined.putAll(request.getSystemHeadersBuilder().map());
357-
combined.putAll(request.headers().map()); // let user override system
358-
359-
// keep only proxy-* - and also strip authorization headers
360-
// for disabled schemes
361-
return HttpHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
355+
private static ProxyHeaders proxyTunnelHeaders(HttpRequestImpl request) {
356+
HttpHeaders userHeaders = HttpHeaders.of(request.headers().map(), Utils.PROXY_TUNNEL_FILTER);
357+
HttpHeaders systemHeaders = HttpHeaders.of(request.getSystemHeadersBuilder().map(), Utils.PROXY_TUNNEL_FILTER);
358+
return new ProxyHeaders(userHeaders, systemHeaders);
362359
}
363360

364361
/* Returns either a plain HTTP connection or a plain tunnelling connection

src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import jdk.internal.net.http.websocket.WebSocketRequest;
4949

5050
import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
51+
import static jdk.internal.net.http.common.Utils.ProxyHeaders;
5152

5253
public class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
5354

@@ -203,14 +204,15 @@ private BodyPublisher publisher(HttpRequestImpl other) {
203204
}
204205

205206
/* used for creating CONNECT requests */
206-
HttpRequestImpl(String method, InetSocketAddress authority, HttpHeaders headers) {
207+
HttpRequestImpl(String method, InetSocketAddress authority, ProxyHeaders headers) {
207208
// TODO: isWebSocket flag is not specified, but the assumption is that
208209
// such a request will never be made on a connection that will be returned
209210
// to the connection pool (we might need to revisit this constructor later)
210211
assert "CONNECT".equalsIgnoreCase(method);
211212
this.method = method;
212213
this.systemHeadersBuilder = new HttpHeadersBuilder();
213-
this.userHeaders = headers;
214+
this.systemHeadersBuilder.map().putAll(headers.systemHeaders().map());
215+
this.userHeaders = headers.userHeaders();
214216
this.uri = URI.create("socket://" + authority.getHostString() + ":"
215217
+ Integer.toString(authority.getPort()) + "/");
216218
this.proxy = null;

src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import jdk.internal.net.http.common.FlowTube;
3939
import jdk.internal.net.http.common.MinimalFuture;
4040
import static java.net.http.HttpResponse.BodyHandlers.discarding;
41+
import static jdk.internal.net.http.common.Utils.ProxyHeaders;
4142

4243
/**
4344
* A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
@@ -47,14 +48,14 @@
4748
final class PlainTunnelingConnection extends HttpConnection {
4849

4950
final PlainHttpConnection delegate;
50-
final HttpHeaders proxyHeaders;
51+
final ProxyHeaders proxyHeaders;
5152
final InetSocketAddress proxyAddr;
5253
private volatile boolean connected;
5354

5455
protected PlainTunnelingConnection(InetSocketAddress addr,
5556
InetSocketAddress proxy,
5657
HttpClientImpl client,
57-
HttpHeaders proxyHeaders) {
58+
ProxyHeaders proxyHeaders) {
5859
super(addr, client);
5960
this.proxyAddr = proxy;
6061
this.proxyHeaders = proxyHeaders;

src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,13 @@ private static Set<String> getDisallowedHeaders() {
175175
// used by caller.
176176

177177
public static final BiPredicate<String, String> CONTEXT_RESTRICTED(HttpClient client) {
178-
return (k, v) -> client.authenticator() == null ||
179-
! (k.equalsIgnoreCase("Authorization")
180-
&& k.equalsIgnoreCase("Proxy-Authorization"));
178+
return (k, v) -> !client.authenticator().isPresent() ||
179+
(!k.equalsIgnoreCase("Authorization")
180+
&& !k.equalsIgnoreCase("Proxy-Authorization"));
181181
}
182+
183+
public record ProxyHeaders(HttpHeaders userHeaders, HttpHeaders systemHeaders) {}
184+
182185
private static final BiPredicate<String, String> HOST_RESTRICTED = (k,v) -> !"host".equalsIgnoreCase(k);
183186
public static final BiPredicate<String, String> PROXY_TUNNEL_RESTRICTED(HttpClient client) {
184187
return CONTEXT_RESTRICTED(client).and(HOST_RESTRICTED);
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import com.sun.net.httpserver.Headers;
25+
import com.sun.net.httpserver.HttpExchange;
26+
import com.sun.net.httpserver.HttpHandler;
27+
import com.sun.net.httpserver.HttpServer;
28+
29+
import java.io.*;
30+
import java.net.Authenticator;
31+
import java.net.InetSocketAddress;
32+
import java.net.ProxySelector;
33+
import java.net.URI;
34+
import java.net.http.HttpClient;
35+
import java.net.http.HttpRequest;
36+
import java.net.http.HttpResponse;
37+
import java.nio.channels.*;
38+
import java.nio.charset.StandardCharsets;
39+
40+
import jdk.test.lib.net.IPSupport;
41+
42+
/**
43+
* @test
44+
* @bug 8263442
45+
* @summary Potential bug in jdk.internal.net.http.common.Utils.CONTEXT_RESTRICTED
46+
* @library /test/lib
47+
* @run main/othervm AuthFilter
48+
*/
49+
50+
public class AuthFilter {
51+
static class Auth extends Authenticator {
52+
}
53+
54+
static HttpServer createServer() throws IOException {
55+
HttpServer server = HttpServer.create(new InetSocketAddress(0), 5);
56+
HttpHandler handler = (HttpExchange e) -> {
57+
InputStream is = e.getRequestBody();
58+
is.readAllBytes();
59+
is.close();
60+
Headers reqh = e.getRequestHeaders();
61+
if (reqh.containsKey("authorization")) {
62+
e.sendResponseHeaders(500, -1);
63+
} else {
64+
e.sendResponseHeaders(200, -1);
65+
}
66+
};
67+
server.createContext("/", handler);
68+
return server;
69+
}
70+
71+
public static void main(String[] args) throws Exception {
72+
test(false);
73+
test(true);
74+
}
75+
76+
/**
77+
* Fake proxy. Just looks for Proxy-Authorization header
78+
* and returns error if seen. Returns 200 OK if not.
79+
* Does not actually forward the request
80+
*/
81+
static class ProxyServer extends Thread {
82+
83+
final ServerSocketChannel server;
84+
final int port;
85+
volatile SocketChannel c;
86+
87+
ProxyServer() throws IOException {
88+
server = ServerSocketChannel.open();
89+
server.bind(new InetSocketAddress(0));
90+
if (server.getLocalAddress() instanceof InetSocketAddress isa) {
91+
port = isa.getPort();
92+
} else {
93+
port = -1;
94+
}
95+
}
96+
97+
int getPort() {
98+
return port;
99+
}
100+
101+
static String ok = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
102+
static String notok1 = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n";
103+
static String notok2 = "HTTP/1.1 501 Not Implemented\r\nContent-Length: 0\r\n\r\n";
104+
105+
static void reply(String msg, Writer writer) throws IOException {
106+
writer.write(msg);
107+
writer.flush();
108+
}
109+
110+
public void run() {
111+
try {
112+
c = server.accept();
113+
var cs = StandardCharsets.US_ASCII;
114+
LineNumberReader reader = new LineNumberReader(Channels.newReader(c, cs));
115+
Writer writer = Channels.newWriter(c, cs);
116+
117+
String line;
118+
while ((line=reader.readLine()) != null) {
119+
if (line.indexOf("Proxy-Authorization") != -1) {
120+
reply(notok1, writer);
121+
return;
122+
}
123+
if (line.equals("")) {
124+
// end of headers
125+
reply(ok, writer);
126+
return;
127+
}
128+
}
129+
reply(notok2, writer);
130+
} catch (IOException e) {
131+
}
132+
try {
133+
server.close();
134+
c.close();
135+
} catch (IOException ee) {}
136+
}
137+
}
138+
139+
private static InetSocketAddress getLoopback(int port) throws IOException {
140+
if (IPSupport.hasIPv4()) {
141+
return new InetSocketAddress("127.0.0.1", port);
142+
} else {
143+
return new InetSocketAddress("::1", port);
144+
}
145+
}
146+
147+
public static void test(boolean useProxy) throws Exception {
148+
HttpServer server = createServer();
149+
int port = server.getAddress().getPort();
150+
ProxyServer proxy;
151+
152+
InetSocketAddress proxyAddr;
153+
String authHdr;
154+
if (useProxy) {
155+
proxy = new ProxyServer();
156+
proxyAddr = getLoopback(proxy.getPort());
157+
proxy.start();
158+
authHdr = "Proxy-Authorization";
159+
} else {
160+
authHdr = "Authorization";
161+
proxyAddr = null;
162+
}
163+
164+
server.start();
165+
166+
// proxyAddr == null => proxying disabled
167+
HttpClient client = HttpClient
168+
.newBuilder()
169+
.authenticator(new Auth())
170+
.proxy(ProxySelector.of(proxyAddr))
171+
.build();
172+
173+
174+
URI uri = new URI("http://127.0.0.1:" + Integer.toString(port));
175+
176+
HttpRequest request = HttpRequest.newBuilder(uri)
177+
.header(authHdr, "nonsense")
178+
.GET()
179+
.build();
180+
181+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
182+
int r = response.statusCode();
183+
System.out.println(r);
184+
server.stop(0);
185+
if (r != 200)
186+
throw new RuntimeException("Test failed : " + r);
187+
}
188+
}

0 commit comments

Comments
 (0)