Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ Changes log
- The HTTP client connects directly to the hosts listed in the "nonProxyHosts" parameter
or the "http.nonProxyHosts" system property, without proxy. As with the JDK, the
loopback addresses are excluded by default. Issue #401.
- The HTTP client supports SOCKS5 proxies, with optional username/password
authentication: "socksProxyHost", "socksProxyPort", "socksProxyUsername" and
"socksProxyPassword" parameters, defaulting to the "socksProxyHost", "socksProxyPort",
"java.net.socks.username" and "java.net.socks.password" system properties. An HTTP
proxy takes precedence. Issue #1573.
- Added an optional origin matcher to CORS (`CorsService`, `CorsFilter`, `setAllowedOriginMatcher(Predicate<String>)`) to accept origins that are not listed, for example any sub-domain. The response always contains the exact request origin. Issue #1611.
- Added the `SameSite` attribute to cookie settings (`CookieSetting.get/setSameSite()`,
`SameSite` enum), read and written in `Set-Cookie`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ void handle(org.restlet.Request request, org.restlet.Response response) {
if (isTunneled()) {
moveTunnelProxyAuthorization(request);
} else if (!isForwardProxied()) {
// Direct connection: the proxy credentials must not reach the origin server.
// Direct or SOCKS connection: the proxy credentials must not reach the origin server.
requestHeaders.removeAll(HeaderConstants.HEADER_PROXY_AUTHORIZATION, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.ProxyConnectException;
import io.netty.handler.proxy.ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
Expand Down Expand Up @@ -171,7 +173,7 @@
* <td>nonProxyHosts</td>
* <td>String</td>
* <td>System property "http.nonProxyHosts" or "localhost|127.*|[::1]|0.0.0.0|[::0]"</td>
* <td>The hosts to connect to directly, without proxy, separated by "|". A host name can start or
* <td>The hosts to connect to directly, without HTTP or SOCKS proxy, separated by "|". A host name can start or
* end with a "*" wildcard, e.g. "localhost|*.example.com|192.168.*", and is matched ignoring
* case. As with the JDK, the loopback addresses are excluded by default, and an empty value
* excludes no host.</td>
Expand All @@ -197,6 +199,32 @@
* <td>The size in bytes of the buffer used to write request entities</td>
* </tr>
* <tr>
* <td>socksProxyHost</td>
* <td>String</td>
* <td>System property "socksProxyHost"</td>
* <td>The host name of the SOCKS5 proxy, used for the requests without HTTP proxy. The SOCKS
* proxy resolves the host names of the origin servers.</td>
* </tr>
* <tr>
* <td>socksProxyPort</td>
* <td>int</td>
* <td>System property "socksProxyPort" or "1080"</td>
* <td>The port of the SOCKS5 proxy.</td>
* </tr>
* <tr>
* <td>socksProxyUsername</td>
* <td>String</td>
* <td>System property "java.net.socks.username"</td>
* <td>The user name for the username/password authentication with the SOCKS5 proxy, if
* required.</td>
* </tr>
* <tr>
* <td>socksProxyPassword</td>
* <td>String</td>
* <td>System property "java.net.socks.password"</td>
* <td>The password for the username/password authentication with the SOCKS5 proxy.</td>
* </tr>
* <tr>
* <td>userAgentField</td>
* <td>String</td>
* <td>null</td>
Expand Down Expand Up @@ -485,7 +513,8 @@ private static void resetResponse(Response response, Request next) {
Channel acquire(NettyClientCall call) throws IOException {
if (call.isConfidential()
&& getTransportMode() == TransportMode.HTTP3
&& getProxy(true, call.getTargetHost()) == null) {
&& getProxy(true, call.getTargetHost()) == null
&& getSocksProxy(call.getTargetHost()) == null) {
return acquireHttp3(call);
}

Expand Down Expand Up @@ -680,11 +709,9 @@ private Channel connect(final OriginPool pool, final String proxyAuthorization)
boolean acquired = true;
try {
final SslContext ssl = pool.secure ? getSslContext() : null;
final InetSocketAddress proxyRef = getProxy(pool.secure, pool.host);
final InetSocketAddress proxy =
(proxyRef == null)
? null
: new InetSocketAddress(proxyRef.getHostString(), proxyRef.getPort());
final InetSocketAddress proxy = resolve(getProxy(pool.secure, pool.host));
final InetSocketAddress socksProxy =
(proxy == null) ? resolve(getSocksProxy(pool.host)) : null;

Bootstrap bootstrap =
new Bootstrap()
Expand All @@ -700,7 +727,12 @@ private Channel connect(final OriginPool pool, final String proxyAuthorization)
@Override
protected void initChannel(SocketChannel ch) {
initConnection(
ch, pool, ssl, proxy, proxyAuthorization);
ch,
pool,
ssl,
proxy,
proxyAuthorization,
socksProxy);
}
});

Expand All @@ -710,8 +742,8 @@ protected void initChannel(SocketChannel ch) {
}

SocketAddress remote;
if (proxy != null && pool.secure) {
// Tunnel with CONNECT, the proxy resolves the host name.
if ((proxy != null && pool.secure) || (socksProxy != null)) {
// Tunnel with CONNECT or SOCKS, the proxy resolves the host name.
bootstrap.resolver(NoopAddressResolverGroup.INSTANCE);
remote = InetSocketAddress.createUnresolved(pool.host, pool.port);
} else if (proxy != null) {
Expand Down Expand Up @@ -806,7 +838,8 @@ private void initConnection(
OriginPool pool,
SslContext ssl,
InetSocketAddress proxy,
String proxyAuthorization) {
String proxyAuthorization,
InetSocketAddress socksProxy) {
final Promise<String> ready = ch.eventLoop().newPromise();
ch.attr(READY).set(ready);
ch.attr(POOL).set(pool);
Expand All @@ -820,16 +853,22 @@ private void initConnection(
}
p.addLast("writability", Writability.HANDLER);

if ((proxy == null) || !pool.secure) {
ProxyHandler proxyHandler;
if (socksProxy != null) {
proxyHandler =
new Socks5ProxyHandler(
socksProxy, getSocksProxyUsername(), getSocksProxyPassword());
Comment thread
jlouvel marked this conversation as resolved.
Resolved
} else if ((proxy != null) && pool.secure) {
HttpHeaders connectHeaders = new DefaultHttpHeaders();
if (proxyAuthorization != null) {
connectHeaders.set(HttpHeaderNames.PROXY_AUTHORIZATION, proxyAuthorization);
}
proxyHandler = new HttpProxyHandler(proxy, connectHeaders);
} else {
initProtocols(ch, pool, ssl, ready, proxy != null);
return;
}

HttpHeaders connectHeaders = new DefaultHttpHeaders();
if (proxyAuthorization != null) {
connectHeaders.set(HttpHeaderNames.PROXY_AUTHORIZATION, proxyAuthorization);
}
HttpProxyHandler proxyHandler = new HttpProxyHandler(proxy, connectHeaders);
proxyHandler.setConnectTimeoutMillis(getConnectTimeout());
p.addLast("proxy", proxyHandler);
p.addLast(
Expand Down Expand Up @@ -1166,6 +1205,73 @@ InetSocketAddress getProxy(boolean secure, String targetHost) {
host, secure ? getHttpsProxyPort() : getProxyPort());
}

/**
* Returns the SOCKS5 proxy to connect to an origin server through, or null to connect directly.
* An HTTP proxy, if any for the request, takes precedence.
*
* @param targetHost The host name of the origin server.
* @return The unresolved address of the SOCKS5 proxy, or null.
*/
InetSocketAddress getSocksProxy(String targetHost) {
String host = getSocksProxyHost();
return ((host == null) || host.isEmpty() || isNonProxyHost(targetHost))
? null
: InetSocketAddress.createUnresolved(host, getSocksProxyPort());
}

/**
* Returns the host name of the SOCKS5 proxy, if specified: the "socksProxyHost" parameter, or
* the "socksProxyHost" system property.
*
* @return The host name of the SOCKS5 proxy, if specified.
*/
public String getSocksProxyHost() {
return getHelpedParameters()
.getFirstValue("socksProxyHost", System.getProperty("socksProxyHost"));
}

/**
* Returns the port of the SOCKS5 proxy: the "socksProxyPort" parameter, or the "socksProxyPort"
* system property, 1080 otherwise.
*
* @return The port of the SOCKS5 proxy.
*/
public int getSocksProxyPort() {
return Integer.parseInt(
getHelpedParameters()
.getFirstValue(
"socksProxyPort", System.getProperty("socksProxyPort", "1080")));
}

/**
* Returns the user name for the authentication with the SOCKS5 proxy, if specified: the
* "socksProxyUsername" parameter, or the "java.net.socks.username" system property.
*
* @return The user name, or null.
*/
public String getSocksProxyUsername() {
return getHelpedParameters()
.getFirstValue("socksProxyUsername", System.getProperty("java.net.socks.username"));
}

/**
* Returns the password for the authentication with the SOCKS5 proxy, if specified: the
* "socksProxyPassword" parameter, or the "java.net.socks.password" system property.
*
* @return The password, or null.
*/
public String getSocksProxyPassword() {
return getHelpedParameters()
.getFirstValue("socksProxyPassword", System.getProperty("java.net.socks.password"));
}

/** Resolves the host name of a proxy address, if any. */
private static InetSocketAddress resolve(InetSocketAddress address) {
return (address == null)
? null
: new InetSocketAddress(address.getHostString(), address.getPort());
}

/**
* Returns the hosts to connect to directly, without proxy, separated by "|": the
* "nonProxyHosts" parameter, or the "http.nonProxyHosts" system property, the loopback
Expand Down
Loading
Loading