public static String perConnectionAuthorizationHeader( Request request, ProxyServer proxyServer, Realm realm) { String authorizationHeader = null; if (realm != null && realm.getUsePreemptiveAuth()) { switch (realm.getScheme()) { case NTLM: String msg = NtlmEngine.INSTANCE.generateType1Msg(); authorizationHeader = "NTLM " + msg; break; case KERBEROS: case SPNEGO: String host; if (proxyServer != null) host = proxyServer.getHost(); else if (request.getVirtualHost() != null) host = request.getVirtualHost(); else host = request.getUri().getHost(); authorizationHeader = "Negotiate " + SpnegoEngine.instance().generateToken(host); break; default: break; } } return authorizationHeader; }
@Test(groups = "standalone") public void testNonProxyHost() { // // should avoid, it's in non-proxy hosts Request req = get("http://somewhere.com/foo").build(); ProxyServer proxyServer = proxyServer("foo", 1234).setNonProxyHost("somewhere.com").build(); assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); // // // should avoid, it's in non-proxy hosts (with "*") req = get("http://sub.somewhere.com/foo").build(); proxyServer = proxyServer("foo", 1234).setNonProxyHost("*.somewhere.com").build(); assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); // should use it req = get("http://sub.somewhere.com/foo").build(); proxyServer = proxyServer("foo", 1234).setNonProxyHost("*.somewhere.com").build(); assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); }
private void kerberosChallenge( Channel channel, // List<String> authHeaders, // Request request, // HttpHeaders headers, // Realm realm, // NettyResponseFuture<?> future) throws SpnegoEngineException { Uri uri = request.getUri(); String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); String challengeHeader = SpnegoEngine.instance().generateToken(host); headers.set(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); }
private boolean exitAfterHandlingConnect( // final Channel channel, // final NettyResponseFuture<?> future, // final Request request, // ProxyServer proxyServer, // int statusCode, // HttpRequest httpRequest) throws IOException { if (future.isKeepAlive()) future.attachChannel(channel, true); Uri requestUri = request.getUri(); logger.debug("Connecting to proxy {} for scheme {}", proxyServer, requestUri.getScheme()); channelManager.upgradeProtocol(channel.pipeline(), requestUri); future.setReuseChannel(true); future.setConnectAllowed(false); requestSender.drainChannelAndExecuteNextRequest( channel, future, new RequestBuilder(future.getTargetRequest()).build()); return true; }
private boolean exitAfterHandling407( // Channel channel, // NettyResponseFuture<?> future, // HttpResponse response, // Request request, // int statusCode, // ProxyServer proxyServer, // HttpRequest httpRequest) { if (future.getInProxyAuth().getAndSet(true)) { logger.info("Can't handle 407 as auth was already performed"); return false; } Realm proxyRealm = future.getProxyRealm(); if (proxyRealm == null) { logger.info("Can't handle 407 as there's no proxyRealm"); return false; } List<String> proxyAuthHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); if (proxyAuthHeaders.isEmpty()) { logger.info("Can't handle 407 as response doesn't contain Proxy-Authenticate headers"); return false; } // FIXME what's this??? future.setChannelState(ChannelState.NEW); HttpHeaders requestHeaders = new DefaultHttpHeaders(false).add(request.getHeaders()); switch (proxyRealm.getScheme()) { case BASIC: if (getHeaderWithPrefix(proxyAuthHeaders, "Basic") == null) { logger.info( "Can't handle 407 with Basic realm as Proxy-Authenticate headers don't match"); return false; } if (proxyRealm.isUsePreemptiveAuth()) { // FIXME do we need this, as future.getAndSetAuth // was tested above? // auth was already performed, most likely auth // failed logger.info( "Can't handle 407 with Basic realm as auth was preemptive and already performed"); return false; } // FIXME do we want to update the realm, or directly // set the header? Realm newBasicRealm = realm(proxyRealm) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newBasicRealm); break; case DIGEST: String digestHeader = getHeaderWithPrefix(proxyAuthHeaders, "Digest"); if (digestHeader == null) { logger.info( "Can't handle 407 with Digest realm as Proxy-Authenticate headers don't match"); return false; } Realm newDigestRealm = realm(proxyRealm) // .setUri(request.getUri()) // .setMethodName(request.getMethod()) // .setUsePreemptiveAuth(true) // .parseProxyAuthenticateHeader(digestHeader) // .build(); future.setProxyRealm(newDigestRealm); break; case NTLM: String ntlmHeader = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); if (ntlmHeader == null) { logger.info("Can't handle 407 with NTLM realm as Proxy-Authenticate headers don't match"); return false; } ntlmProxyChallenge(ntlmHeader, request, proxyRealm, requestHeaders, future); Realm newNtlmRealm = realm(proxyRealm) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newNtlmRealm); break; case KERBEROS: case SPNEGO: if (getHeaderWithPrefix(proxyAuthHeaders, "Negociate") == null) { logger.info( "Can't handle 407 with Kerberos or Spnego realm as Proxy-Authenticate headers don't match"); return false; } try { kerberosProxyChallenge( channel, proxyAuthHeaders, request, proxyServer, proxyRealm, requestHeaders, future); } catch (SpnegoEngineException e) { // FIXME String ntlmHeader2 = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); if (ntlmHeader2 != null) { logger.warn("Kerberos/Spnego proxy auth failed, proceeding with NTLM"); ntlmChallenge(ntlmHeader2, request, requestHeaders, proxyRealm, future); Realm newNtlmRealm2 = realm(proxyRealm) // .setScheme(AuthScheme.NTLM) // .setUsePreemptiveAuth(true) // .build(); future.setProxyRealm(newNtlmRealm2); } else { requestSender.abort(channel, future, e); return false; } } break; default: throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme()); } RequestBuilder nextRequestBuilder = new RequestBuilder(future.getCurrentRequest()).setHeaders(requestHeaders); if (future.getCurrentRequest().getUri().isSecured()) { nextRequestBuilder.setMethod(CONNECT); } final Request nextRequest = nextRequestBuilder.build(); logger.debug("Sending proxy authentication to {}", request.getUri()); if (future.isKeepAlive() // && HttpHeaders.isKeepAlive(httpRequest) // && HttpHeaders.isKeepAlive(response) // // support broken Proxy-Connection && !response.headers().contains("Proxy-Connection", HttpHeaders.Values.CLOSE, true) // && !HttpHeaders.isTransferEncodingChunked(httpRequest) // && !HttpHeaders.isTransferEncodingChunked(response)) { future.setConnectAllowed(true); future.setReuseChannel(true); requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); } else { channelManager.closeChannel(channel); requestSender.sendNextRequest(nextRequest, future); } return true; }
public NettyRequest newNettyRequest( Request request, boolean forceConnect, ProxyServer proxyServer) throws IOException { Uri uri = request.getUri(); HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); boolean connect = method == HttpMethod.CONNECT; boolean allowConnectionPooling = config.isAllowPoolingConnections() && (!HttpUtils.isSecure(uri) || config.isAllowPoolingSslConnections()); HttpVersion httpVersion = !allowConnectionPooling || (connect && proxyServer.isForceHttp10()) ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1; String requestUri = requestUri(uri, proxyServer, connect); NettyBody body = body(request, connect); HttpRequest httpRequest; NettyRequest nettyRequest; if (body instanceof NettyDirectBody) { ChannelBuffer buffer = NettyDirectBody.class.cast(body).channelBuffer(); httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); // body is passed as null as it's written directly with the request httpRequest.setContent(buffer); nettyRequest = new NettyRequest(httpRequest, null); } else { httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); nettyRequest = new NettyRequest(httpRequest, body); } HttpHeaders headers = httpRequest.headers(); if (!connect) { // assign headers as configured on request for (Entry<String, List<String>> header : request.getHeaders()) { headers.set(header.getKey(), header.getValue()); } if (isNonEmpty(request.getCookies())) headers.set(COOKIE, CookieEncoder.encode(request.getCookies())); if (config.isCompressionEnforced() && !headers.contains(ACCEPT_ENCODING)) headers.set(ACCEPT_ENCODING, GZIP_DEFLATE); } if (body != null) { if (body.getContentLength() < 0) headers.set(TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); else headers.set(CONTENT_LENGTH, body.getContentLength()); if (body.getContentType() != null) headers.set(CONTENT_TYPE, body.getContentType()); } // connection header and friends boolean webSocket = isWebSocket(uri.getScheme()); if (!connect && webSocket) { String origin = "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort()); headers .set(UPGRADE, HttpHeaders.Values.WEBSOCKET) // .set(CONNECTION, HttpHeaders.Values.UPGRADE) // .set(ORIGIN, origin) // .set(SEC_WEBSOCKET_KEY, getKey()) // .set(SEC_WEBSOCKET_VERSION, "13"); } else if (!headers.contains(CONNECTION)) { String connectionHeaderValue = connectionHeader(allowConnectionPooling, httpVersion == HttpVersion.HTTP_1_1); if (connectionHeaderValue != null) headers.set(CONNECTION, connectionHeaderValue); } if (!headers.contains(HOST)) headers.set(HOST, hostHeader(request, uri)); Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); // don't override authorization but append addAuthorizationHeader(headers, systematicAuthorizationHeader(request, realm)); setProxyAuthorizationHeader( headers, systematicProxyAuthorizationHeader(request, proxyServer, realm, connect)); // Add default accept headers if (!headers.contains(ACCEPT)) headers.set(ACCEPT, "*/*"); // Add default user agent if (!headers.contains(USER_AGENT) && config.getUserAgent() != null) headers.set(USER_AGENT, config.getUserAgent()); return nettyRequest; }
protected boolean exitAfterHandlingRedirect( // Channel channel, // NettyResponseFuture<?> future, // HttpResponse response, // Request request, // int statusCode) throws Exception { if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); } else { // We must allow 401 handling again. future.getAndSetAuth(false); HttpHeaders responseHeaders = response.headers(); String location = responseHeaders.get(HttpHeaders.Names.LOCATION); Uri uri = Uri.create(future.getUri(), location); if (!uri.equals(future.getUri())) { final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest()); if (!config.isRemoveQueryParamOnRedirect()) requestBuilder.addQueryParams(future.getRequest().getQueryParams()); // if we are to strictly handle 302, we should keep the original method (which browsers // don't) // 303 must force GET if ((statusCode == FOUND.code() && !config.isStrict302Handling()) || statusCode == SEE_OTHER.code()) requestBuilder.setMethod("GET"); // in case of a redirect from HTTP to HTTPS, future attributes might change final boolean initialConnectionKeepAlive = future.isKeepAlive(); final String initialPoolKey = channelManager.getPartitionId(future); future.setUri(uri); String newUrl = uri.toUrl(); if (request.getUri().getScheme().startsWith(WEBSOCKET)) { newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET); } logger.debug("Redirecting to {}", newUrl); for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { Cookie c = CookieDecoder.decode(cookieStr, timeConverter); if (c != null) requestBuilder.addOrReplaceCookie(c); } Callback callback = channelManager.newDrainCallback( future, channel, initialConnectionKeepAlive, initialPoolKey); if (HttpHeaders.isTransferEncodingChunked(response)) { // We must make sure there is no bytes left before // executing the next request. // FIXME investigate this Channels.setAttribute(channel, callback); } else { // FIXME don't understand: this offers the connection to the pool, or even closes it, // while the // request has not been sent, right? callback.call(); } Request redirectRequest = requestBuilder.setUrl(newUrl).build(); // FIXME why not reuse the channel is same host? requestSender.sendNextRequest(redirectRequest, future); return true; } } } return false; }