/* * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/** * A minimal proxy server that supports CONNECT tunneling. It does not do * any header transformations. In future this could be added. * Two threads are created per client connection. So, it's not * intended for large numbers of parallel connections.
*/ publicclass ProxyServer extendsThreadimplements Closeable {
// could use the test library here - Platform.isWindows(), // but it would force all tests that use ProxyServer to // build it. Let's keep it simple. staticfinalboolean IS_WINDOWS; static {
PrivilegedAction<String> action =
() -> System.getProperty("os.name", "unknown");
String osName = AccessController.doPrivileged(action);
IS_WINDOWS = osName.toLowerCase(Locale.ROOT).startsWith("win");
}
/** * Create proxy on port (zero means don't care). Call getPort() * to get the assigned port.
*/ public ProxyServer(Integer port) throws IOException { this(port, false);
}
public ProxyServer(Integer port, Boolean debug,
String username,
String password) throws IOException
{ this(port, debug, new Credentials(username, password));
}
public ProxyServer(String s) {
credentials = null;
connections = new CopyOnWriteArrayList<Connection>();
}
/** * Returns the port number this proxy is listening on
*/ publicint getPort() { return port;
}
public InetSocketAddress getProxyAddress() throws IOException { return (InetSocketAddress)listener.getLocalAddress();
}
/** * Shuts down the proxy, probably aborting any connections * currently open
*/ publicvoid close() throws IOException { if (debug) System.out.println("Proxy: closing server");
done = true;
listener.close(); for (Connection c : connections) {
c.close();
c.awaitCompletion();
}
}
final CopyOnWriteArrayList<Connection> connections;
volatileboolean done;
publicvoid run() { if (System.getSecurityManager() == null) {
execute();
} else { // so calling domain does not need to have socket permission
AccessController.doPrivileged(new PrivilegedAction<Void>() { publicVoid run() {
execute(); returnnull;
}
});
}
}
publicvoid execute() { int id = 0; try { while (!done) {
SocketChannel s = listener.accept();
id++;
Connection c = new Connection(s, id); if (debug)
System.out.println("Proxy: accepted new connection: " + c);
connections.add(c);
c.init();
}
} catch(Throwable e) { if (debug && !done) {
System.out.println("Proxy: Fatal error, listener got " + e);
e.printStackTrace();
}
}
}
/** * Transparently forward everything, once we know what the destination is
*/ class Connection {
privatevolatileboolean closing; publicsynchronizedvoid close() throws IOException {
closing = true; if (debug)
System.out.println("Proxy: closing connection {" + this + "}"); if (serverSocket != null)
serverSocket.close(); if (clientSocket != null)
clientSocket.close();
}
publicvoid awaitCompletion() { try { if (in != null)
in.join(); if (out!= null)
out.join();
} catch (InterruptedException e) { }
}
int findCRLF(byte[] b) { for (int i=0; i<b.length-1; i++) { if (b[i] == CR && b[i+1] == LF) { return i;
}
} return -1;
}
// Checks credentials in the request against those allowable by the proxy. privateboolean authorized(Credentials credentials,
List<String> requestHeaders) {
List<String> authorization = requestHeaders.stream()
.filter(n -> n.toLowerCase(Locale.US).startsWith("proxy-authorization"))
.collect(toList());
if (authorization.isEmpty()) returnfalse;
if (authorization.size() != 1) { thrownew IllegalStateException("Authorization unexpected count:" + authorization);
}
String value = authorization.get(0).substring("proxy-authorization".length()).trim(); if (!value.startsWith(":")) thrownew IllegalStateException("Authorization malformed: " + value);
value = value.substring(1).trim();
if (!value.startsWith("Basic ")) thrownew IllegalStateException("Authorization not Basic: " + value);
value = value.substring("Basic ".length());
String values = new String(Base64.getDecoder().decode(value), UTF_8); int sep = values.indexOf(':'); if (sep < 1) { thrownew IllegalStateException("Authorization no colon: " + values);
}
String name = values.substring(0, sep);
String password = values.substring(sep + 1);
if (name.equals(credentials.name()) && password.equals(credentials.password())) returntrue;
returnfalse;
}
publicvoid init() { try { byte[] buf;
String host;
List<String> headers; boolean authorized = false; while (true) {
buf = readHeaders(clientIn); if (findCRLF(buf) == -1) { if (debug)
System.out.println("Proxy: no CRLF closing, buf contains:["
+ new String(buf, ISO_8859_1) + "]" );
close(); return;
}
headers = asList(new String(buf, ISO_8859_1).split("\r\n"));
host = findFirst(headers, "host"); // check authorization credentials, if required by the server if (credentials != null) { if (!authorized(credentials, headers)) {
String resp = "HTTP/1.1 407 Proxy Authentication Required\r\n" + "Content-Length: 0\r\n" + "Proxy-Authenticate: Basic realm=\"proxy realm\"\r\n\r\n";
clientSocket.setOption(StandardSocketOptions.TCP_NODELAY, true);
clientSocket.setOption(StandardSocketOptions.SO_LINGER, 2); var buffer = ByteBuffer.wrap(resp.getBytes(ISO_8859_1));
clientSocket.write(buffer); if (debug) { var linger = clientSocket.getOption(StandardSocketOptions.SO_LINGER); var nodelay = clientSocket.getOption(StandardSocketOptions.TCP_NODELAY);
System.out.printf("Proxy: unauthorized; 407 sent (%s/%s), linger: %s, nodelay: %s%n",
buffer.position(), buffer.position() + buffer.remaining(), linger, nodelay);
} if (shouldCloseAfter407(headers)) {
closeConnection(); return;
} continue;
}
authorized = true; break;
} else { break;
}
}
int p = findCRLF(buf);
String cmd = new String(buf, 0, p, ISO_8859_1);
String[] params = cmd.split(" ");
if (params[0].equals("CONNECT")) {
doTunnel(params[1]);
} else { // TODO: // this does not really work as it should: it also establishes // a tunnel (proxyCommon). So it works as long as the client only // sends a single plain request through the proxy - as all // other requests would otherwise be tunneled to the // server identified in the first request. // It seems enough for our purpose for now, though. // Fixing this would imply dealing with Content-Length, Transfer-Encoding, // etc, both when writing to and reading from the server.
doProxy(params[1], cmd, headers, host, authorized);
}
} catch (Throwable e) { if (debug) {
System.out.println("Proxy: " + e);
e.printStackTrace();
} try {close(); } catch (IOException e1) {}
}
}
void closeConnection() throws IOException { if (debug) { var linger = clientSocket.getOption(StandardSocketOptions.SO_LINGER); var nodelay = clientSocket.getOption(StandardSocketOptions.TCP_NODELAY);
System.out.printf("Proxy: closing connection id=%s, linger: %s, nodelay: %s%n",
id, linger, nodelay);
} long drained = drain(clientSocket); if (debug) {
System.out.printf("Proxy: drained: %s%n", drained);
}
clientSocket.shutdownOutput(); try { // On windows, we additionally need to delay before // closing the socket. Otherwise we get a reset on the // client side (The connection was aborted by a software // on the host machine). // Delay 500ms before actually closing the socket if (isWindows()) Thread.sleep(500);
} catch (InterruptedException x) { // OK
}
clientSocket.shutdownInput();
close();
}
// If the client sends a request body we will need to close the connection // otherwise, we can keep it open. privateboolean shouldCloseAfter407(List<String> headers) throws IOException { var te = findFirst(headers, "transfer-encoding"); if (te != null) { // processing transfer encoding not implemented if (debug) {
System.out.println("Proxy: transfer-encoding with 407, closing connection");
} returntrue; // should close
} var cl = findFirst(headers, "content-length"); int n = -1; try {
n = Integer.parseInt(cl); if (debug) {
System.out.printf("Proxy: content-length: %d%n", cl);
}
} catch (IllegalFormatException x) { if (debug) {
System.out.println("Proxy: bad content-length, closing connection");
} returntrue; // should close
} if (n > 0 || n < -1) { if (debug) {
System.out.println("Proxy: request body with 407, closing connection");
} returntrue; // should close
} var cmdline = headers.get(0); int m = cmdline.indexOf(' '); var method = (m > 0) ? cmdline.substring(0, m) : null; var nobody = List.of("GET", "HEAD"); if (n == 0 || nobody.contains(m)) { var available = clientIn.available(); var drained = drain(clientSocket); if (drained > 0 || available > 0) { if (debug) {
System.out.printf("Proxy: unexpected bytes (%d) with 407, closing connection%n",
drained + available);
} returntrue; // should close
} // can keep open: CL=0 or no CL and GET or HEAD returnfalse;
} else { if (debug) {
System.out.println("Proxy: possible body with 407, closing connection");
} returntrue; // should close
}
}
void doProxy(String dest, String cmdLine, List<String> headers, String host, boolean authorized) throws IOException
{ try {
URI uri = new URI(dest); if (!uri.isAbsolute()) { if (host == null) { thrownew IOException("request URI not absolute");
} else {
uri = new URI("http://" + host + dest);
}
} if (debug) System.out.printf("Proxy: uri=%s%n", uri);
dest = uri.getAuthority(); // now extract the path from the URI and recreate the cmd line int sp = cmdLine.indexOf(' ');
String method = cmdLine.substring(0, sp);
cmdLine = method + " " + uri.getPath() + " HTTP/1.1";
commonInit(dest, 80);
OutputStream sout; synchronized (this) { if (closing) return;
sout = serverOut;
} // might fail if we're closing but we don't care. byte[] CRLF = newbyte[] { (byte) '\r', (byte) '\n'};
sout.write(cmdLine.getBytes(ISO_8859_1));
sout.write(CRLF); if (debug) System.out.printf("Proxy Forwarding: %s%n", cmdLine); for (int l=1; l<headers.size(); l++) { var s = headers.get(l); if (!authorized || !s.toLowerCase(Locale.ROOT).startsWith("proxy-authorization")) {
sout.write(s.getBytes(ISO_8859_1));
sout.write(CRLF); if (debug) System.out.printf("Proxy Forwarding: %s%n", s);
} else { if (debug) System.out.printf("Proxy Skipping: %s%n", s);
}
}
sout.write(CRLF); if (debug) System.out.printf("Proxy Forwarding: %n");
// This will now establish a tunnel :-(
proxyCommon(debug);
synchronizedvoid commonInit(String dest, int defaultPort) throws IOException { if (closing) return; int port;
String[] hostport = dest.split(":"); if (hostport.length == 1) {
port = defaultPort;
} else {
port = Integer.parseInt(hostport[1]);
} if (debug)
System.out.printf("Proxy: connecting to (%s/%d)\n", hostport[0], port);
serverSocket = SocketChannel.open();
serverSocket.connect(new InetSocketAddress(hostport[0], port));
serverOut = serverSocket.socket().getOutputStream();
serverIn = new BufferedInputStream(serverSocket.socket().getInputStream());
}
synchronizedvoid proxyCommon(boolean log) throws IOException { if (closing) return;
out = newThread(() -> { try { byte[] bb = newbyte[8000]; int n; int body = 0; while ((n = clientIn.read(bb)) != -1) {
serverOut.write(bb, 0, n);
body += n; if (log)
System.out.printf("Proxy Forwarding [request body]: total %d%n", body);
}
closing = true;
serverSocket.close();
clientSocket.close();
} catch (IOException e) { if (!closing && debug) {
System.out.println("Proxy: " + e);
e.printStackTrace();
}
}
});
in = newThread(() -> { try { byte[] bb = newbyte[8000]; int n; int resp = 0; while ((n = serverIn.read(bb)) != -1) {
clientOut.write(bb, 0, n);
resp += n; if (log) System.out.printf("Proxy Forwarding [response]: %s%n", new String(bb, 0, n, UTF_8)); if (log) System.out.printf("Proxy Forwarding [response]: total %d%n", resp);
}
closing = true;
serverSocket.close();
clientSocket.close();
} catch (IOException e) { if (!closing && debug) {
System.out.println("Proxy: " + e);
e.printStackTrace();
}
}
});
out.setName("Proxy-outbound");
out.setDaemon(true);
in.setDaemon(true);
in.setName("Proxy-inbound");
out.start();
in.start();
}
void doTunnel(String dest) throws IOException { if (closing) return; // no need to go further.
commonInit(dest, 443); // might fail if we're closing, but we don't care.
clientOut.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());
proxyCommon(false);
}
@Override public String toString() { return"Proxy connection " + id + ", client sock:" + clientSocket;
}
}
publicstaticvoid main(String[] args) throws Exception { int port = Integer.parseInt(args[0]); boolean debug = args.length > 1 && args[1].equals("-debug");
System.out.println("Debugging : " + debug);
ProxyServer ps = new ProxyServer(port, debug);
System.out.println("Proxy server listening on port " + ps.getPort()); while (true) { Thread.sleep(5000);
}
}
}
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.