001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.client;
029
030import java.io.IOException;
031import java.net.Socket;
032
033import org.apache.http.ConnectionReuseStrategy;
034import org.apache.http.HttpEntity;
035import org.apache.http.HttpException;
036import org.apache.http.HttpHost;
037import org.apache.http.HttpRequest;
038import org.apache.http.HttpResponse;
039import org.apache.http.HttpVersion;
040import org.apache.http.auth.AUTH;
041import org.apache.http.auth.AuthSchemeRegistry;
042import org.apache.http.auth.AuthScope;
043import org.apache.http.auth.AuthState;
044import org.apache.http.auth.Credentials;
045import org.apache.http.client.config.AuthSchemes;
046import org.apache.http.client.config.RequestConfig;
047import org.apache.http.client.params.HttpClientParamConfig;
048import org.apache.http.client.protocol.HttpClientContext;
049import org.apache.http.client.protocol.RequestClientConnControl;
050import org.apache.http.config.ConnectionConfig;
051import org.apache.http.conn.HttpConnectionFactory;
052import org.apache.http.conn.ManagedHttpClientConnection;
053import org.apache.http.conn.routing.HttpRoute;
054import org.apache.http.conn.routing.RouteInfo.LayerType;
055import org.apache.http.conn.routing.RouteInfo.TunnelType;
056import org.apache.http.entity.BufferedHttpEntity;
057import org.apache.http.impl.DefaultConnectionReuseStrategy;
058import org.apache.http.impl.auth.BasicSchemeFactory;
059import org.apache.http.impl.auth.DigestSchemeFactory;
060import org.apache.http.impl.auth.HttpAuthenticator;
061import org.apache.http.impl.auth.KerberosSchemeFactory;
062import org.apache.http.impl.auth.NTLMSchemeFactory;
063import org.apache.http.impl.auth.SPNegoSchemeFactory;
064import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
065import org.apache.http.impl.execchain.TunnelRefusedException;
066import org.apache.http.message.BasicHttpRequest;
067import org.apache.http.params.BasicHttpParams;
068import org.apache.http.params.HttpParamConfig;
069import org.apache.http.params.HttpParams;
070import org.apache.http.protocol.BasicHttpContext;
071import org.apache.http.protocol.HttpContext;
072import org.apache.http.protocol.HttpCoreContext;
073import org.apache.http.protocol.HttpProcessor;
074import org.apache.http.protocol.HttpRequestExecutor;
075import org.apache.http.protocol.ImmutableHttpProcessor;
076import org.apache.http.protocol.RequestTargetHost;
077import org.apache.http.protocol.RequestUserAgent;
078import org.apache.http.util.Args;
079import org.apache.http.util.EntityUtils;
080
081/**
082 * ProxyClient can be used to establish a tunnel via an HTTP proxy.
083 */
084@SuppressWarnings("deprecation")
085public class ProxyClient {
086
087    private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
088    private final ConnectionConfig connectionConfig;
089    private final RequestConfig requestConfig;
090    private final HttpProcessor httpProcessor;
091    private final HttpRequestExecutor requestExec;
092    private final ProxyAuthenticationStrategy proxyAuthStrategy;
093    private final HttpAuthenticator authenticator;
094    private final AuthState proxyAuthState;
095    private final AuthSchemeRegistry authSchemeRegistry;
096    private final ConnectionReuseStrategy reuseStrategy;
097
098    /**
099     * @since 4.3
100     */
101    public ProxyClient(
102            final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
103            final ConnectionConfig connectionConfig,
104            final RequestConfig requestConfig) {
105        super();
106        this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
107        this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
108        this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT;
109        this.httpProcessor = new ImmutableHttpProcessor(
110                new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent());
111        this.requestExec = new HttpRequestExecutor();
112        this.proxyAuthStrategy = new ProxyAuthenticationStrategy();
113        this.authenticator = new HttpAuthenticator();
114        this.proxyAuthState = new AuthState();
115        this.authSchemeRegistry = new AuthSchemeRegistry();
116        this.authSchemeRegistry.register(AuthSchemes.BASIC, new BasicSchemeFactory());
117        this.authSchemeRegistry.register(AuthSchemes.DIGEST, new DigestSchemeFactory());
118        this.authSchemeRegistry.register(AuthSchemes.NTLM, new NTLMSchemeFactory());
119        this.authSchemeRegistry.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory());
120        this.authSchemeRegistry.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory());
121        this.reuseStrategy = new DefaultConnectionReuseStrategy();
122    }
123
124    /**
125     * @deprecated (4.3) use {@link ProxyClient#ProxyClient(HttpConnectionFactory, ConnectionConfig, RequestConfig)}
126     */
127    @Deprecated
128    public ProxyClient(final HttpParams params) {
129        this(null,
130                HttpParamConfig.getConnectionConfig(params),
131                HttpClientParamConfig.getRequestConfig(params));
132    }
133
134    /**
135     * @since 4.3
136     */
137    public ProxyClient(final RequestConfig requestConfig) {
138        this(null, null, requestConfig);
139    }
140
141    public ProxyClient() {
142        this(null, null, null);
143    }
144
145    /**
146     * @deprecated (4.3) do not use.
147     */
148    @Deprecated
149    public HttpParams getParams() {
150        return new BasicHttpParams();
151    }
152
153    /**
154     * @deprecated (4.3) do not use.
155     */
156    @Deprecated
157    public AuthSchemeRegistry getAuthSchemeRegistry() {
158        return this.authSchemeRegistry;
159    }
160
161    public Socket tunnel(
162            final HttpHost proxy,
163            final HttpHost target,
164            final Credentials credentials) throws IOException, HttpException {
165        Args.notNull(proxy, "Proxy host");
166        Args.notNull(target, "Target host");
167        Args.notNull(credentials, "Credentials");
168        HttpHost host = target;
169        if (host.getPort() <= 0) {
170            host = new HttpHost(host.getHostName(), 80, host.getSchemeName());
171        }
172        final HttpRoute route = new HttpRoute(
173                host,
174                this.requestConfig.getLocalAddress(),
175                proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
176
177        final ManagedHttpClientConnection conn = this.connFactory.create(
178                route, this.connectionConfig);
179        final HttpContext context = new BasicHttpContext();
180        HttpResponse response;
181
182        final HttpRequest connect = new BasicHttpRequest(
183                "CONNECT", host.toHostString(), HttpVersion.HTTP_1_1);
184
185        final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
186        credsProvider.setCredentials(new AuthScope(proxy), credentials);
187
188        // Populate the execution context
189        context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
190        context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
191        context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect);
192        context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
193        context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, this.proxyAuthState);
194        context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
195        context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
196        context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig);
197
198        this.requestExec.preProcess(connect, this.httpProcessor, context);
199
200        for (;;) {
201            if (!conn.isOpen()) {
202                final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
203                conn.bind(socket);
204            }
205
206            this.authenticator.generateAuthResponse(connect, this.proxyAuthState, context);
207
208            response = this.requestExec.execute(connect, conn, context);
209
210            final int status = response.getStatusLine().getStatusCode();
211            if (status < 200) {
212                throw new HttpException("Unexpected response to CONNECT request: " +
213                        response.getStatusLine());
214            }
215            if (this.authenticator.isAuthenticationRequested(proxy, response,
216                    this.proxyAuthStrategy, this.proxyAuthState, context)) {
217                if (this.authenticator.handleAuthChallenge(proxy, response,
218                        this.proxyAuthStrategy, this.proxyAuthState, context)) {
219                    // Retry request
220                    if (this.reuseStrategy.keepAlive(response, context)) {
221                        // Consume response content
222                        final HttpEntity entity = response.getEntity();
223                        EntityUtils.consume(entity);
224                    } else {
225                        conn.close();
226                    }
227                    // discard previous auth header
228                    connect.removeHeaders(AUTH.PROXY_AUTH_RESP);
229                } else {
230                    break;
231                }
232            } else {
233                break;
234            }
235        }
236
237        final int status = response.getStatusLine().getStatusCode();
238
239        if (status > 299) {
240
241            // Buffer response content
242            final HttpEntity entity = response.getEntity();
243            if (entity != null) {
244                response.setEntity(new BufferedHttpEntity(entity));
245            }
246
247            conn.close();
248            throw new TunnelRefusedException("CONNECT refused by proxy: " +
249                    response.getStatusLine(), response);
250        }
251        return conn.getSocket();
252    }
253
254}