001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.http4;
018
019import java.io.File;
020import java.io.IOException;
021import java.net.ProxySelector;
022import java.security.KeyManagementException;
023import java.security.KeyStoreException;
024import java.security.NoSuchAlgorithmException;
025import java.security.cert.CertificateException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.List;
031import java.util.stream.Stream;
032
033import javax.net.ssl.HostnameVerifier;
034import javax.net.ssl.SSLContext;
035
036import org.apache.commons.lang3.StringUtils;
037import org.apache.commons.lang3.time.DurationUtils;
038import org.apache.commons.vfs2.Capability;
039import org.apache.commons.vfs2.FileName;
040import org.apache.commons.vfs2.FileSystem;
041import org.apache.commons.vfs2.FileSystemConfigBuilder;
042import org.apache.commons.vfs2.FileSystemException;
043import org.apache.commons.vfs2.FileSystemOptions;
044import org.apache.commons.vfs2.UserAuthenticationData;
045import org.apache.commons.vfs2.UserAuthenticator;
046import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider;
047import org.apache.commons.vfs2.provider.GenericFileName;
048import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
049import org.apache.http.ConnectionReuseStrategy;
050import org.apache.http.Header;
051import org.apache.http.HttpHost;
052import org.apache.http.auth.AuthScope;
053import org.apache.http.auth.UsernamePasswordCredentials;
054import org.apache.http.client.AuthCache;
055import org.apache.http.client.CookieStore;
056import org.apache.http.client.CredentialsProvider;
057import org.apache.http.client.HttpClient;
058import org.apache.http.client.config.RequestConfig;
059import org.apache.http.client.protocol.HttpClientContext;
060import org.apache.http.config.Registry;
061import org.apache.http.config.RegistryBuilder;
062import org.apache.http.config.SocketConfig;
063import org.apache.http.conn.HttpClientConnectionManager;
064import org.apache.http.conn.routing.HttpRoutePlanner;
065import org.apache.http.conn.socket.ConnectionSocketFactory;
066import org.apache.http.conn.socket.PlainConnectionSocketFactory;
067import org.apache.http.conn.ssl.DefaultHostnameVerifier;
068import org.apache.http.conn.ssl.NoopHostnameVerifier;
069import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
070import org.apache.http.conn.ssl.TrustAllStrategy;
071import org.apache.http.cookie.Cookie;
072import org.apache.http.impl.DefaultConnectionReuseStrategy;
073import org.apache.http.impl.NoConnectionReuseStrategy;
074import org.apache.http.impl.auth.BasicScheme;
075import org.apache.http.impl.client.BasicAuthCache;
076import org.apache.http.impl.client.BasicCookieStore;
077import org.apache.http.impl.client.BasicCredentialsProvider;
078import org.apache.http.impl.client.HttpClientBuilder;
079import org.apache.http.impl.client.HttpClients;
080import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
081import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
082import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
083import org.apache.http.message.BasicHeader;
084import org.apache.http.protocol.HTTP;
085import org.apache.http.ssl.SSLContextBuilder;
086
087/**
088 * {@code FileProvider} implementation using HttpComponents HttpClient library.
089 *
090 * @since 2.3
091 * @deprecated Use {@link org.apache.commons.vfs2.provider.http5}.
092 */
093@Deprecated
094public class Http4FileProvider extends AbstractOriginatingFileProvider {
095
096    /** Authenticator information. */
097    static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES =
098            {
099            UserAuthenticationData.USERNAME,
100            UserAuthenticationData.PASSWORD
101            };
102
103    /** FileProvider capabilities */
104    static final Collection<Capability> CAPABILITIES =
105            Collections.unmodifiableCollection(
106                    Arrays.asList(
107                            Capability.GET_TYPE,
108                            Capability.READ_CONTENT,
109                            Capability.URI,
110                            Capability.GET_LAST_MODIFIED,
111                            Capability.ATTRIBUTES,
112                            Capability.RANDOM_ACCESS_READ,
113                            Capability.DIRECTORY_READ_CONTENT
114                            )
115                    );
116
117    /**
118     * Constructs a new provider.
119     */
120    public Http4FileProvider() {
121        setFileNameParser(Http4FileNameParser.getInstance());
122    }
123
124    private HttpClientConnectionManager createConnectionManager(final Http4FileSystemConfigBuilder builder,
125        final FileSystemOptions fileSystemOptions, final SSLContext sslContext, final HostnameVerifier verifier) {
126        final SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, verifier);
127        // @formatter:off
128        final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
129            .register("https", sslFactory)
130            .register("http", new PlainConnectionSocketFactory())
131            .build();
132        // @formatter:on
133
134        final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
135        connManager.setMaxTotal(builder.getMaxTotalConnections(fileSystemOptions));
136        connManager.setDefaultMaxPerRoute(builder.getMaxConnectionsPerHost(fileSystemOptions));
137
138        // @formatter:off
139        final SocketConfig socketConfig = SocketConfig
140            .custom()
141            .setSoTimeout(DurationUtils.toMillisInt(builder.getSoTimeoutDuration(fileSystemOptions)))
142            .build();
143        // @formatter:on
144
145        connManager.setDefaultSocketConfig(socketConfig);
146
147        return connManager;
148    }
149
150    private CookieStore createDefaultCookieStore(final Http4FileSystemConfigBuilder builder,
151            final FileSystemOptions fileSystemOptions) {
152        final CookieStore cookieStore = new BasicCookieStore();
153        final Cookie[] cookies = builder.getCookies(fileSystemOptions);
154
155        if (cookies != null) {
156            Stream.of(cookies).forEach(cookieStore::addCookie);
157        }
158
159        return cookieStore;
160    }
161
162    private RequestConfig createDefaultRequestConfig(final Http4FileSystemConfigBuilder builder,
163        final FileSystemOptions fileSystemOptions) {
164        return RequestConfig.custom()
165            .setConnectTimeout(DurationUtils.toMillisInt(builder.getConnectionTimeoutDuration(fileSystemOptions)))
166            .build();
167    }
168
169    private HostnameVerifier createHostnameVerifier(final Http4FileSystemConfigBuilder builder,
170        final FileSystemOptions fileSystemOptions) {
171        return builder.isHostnameVerificationEnabled(fileSystemOptions) ? new DefaultHostnameVerifier()
172            : NoopHostnameVerifier.INSTANCE;
173    }
174
175    /**
176     * Create an {@link HttpClient} object for an http4 file system.
177     *
178     * @param builder Configuration options builder for http4 provider
179     * @param rootName The root path
180     * @param fileSystemOptions The file system options
181     * @return an {@link HttpClient} object
182     * @throws FileSystemException if an error occurs.
183     */
184    protected HttpClient createHttpClient(final Http4FileSystemConfigBuilder builder, final GenericFileName rootName,
185            final FileSystemOptions fileSystemOptions) throws FileSystemException {
186        return createHttpClientBuilder(builder, rootName, fileSystemOptions).build();
187    }
188
189    /**
190     * Create an {@link HttpClientBuilder} object. Invoked by {@link #createHttpClient(Http4FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
191     *
192     * @param builder Configuration options builder for HTTP4 provider
193     * @param rootName The root path
194     * @param fileSystemOptions The FileSystem options
195     * @return an {@link HttpClientBuilder} object
196     * @throws FileSystemException if an error occurs
197     */
198    protected HttpClientBuilder createHttpClientBuilder(final Http4FileSystemConfigBuilder builder, final GenericFileName rootName,
199            final FileSystemOptions fileSystemOptions) throws FileSystemException {
200        final List<Header> defaultHeaders = new ArrayList<>();
201        defaultHeaders.add(new BasicHeader(HTTP.USER_AGENT, builder.getUserAgent(fileSystemOptions)));
202
203        final ConnectionReuseStrategy connectionReuseStrategy = builder.isKeepAlive(fileSystemOptions)
204                ? DefaultConnectionReuseStrategy.INSTANCE
205                : NoConnectionReuseStrategy.INSTANCE;
206        final SSLContext sslContext = createSSLContext(builder, fileSystemOptions);
207        final HostnameVerifier hostNameVerifier = createHostnameVerifier(builder, fileSystemOptions);
208        final HttpClientBuilder httpClientBuilder =
209                HttpClients.custom()
210                .setRoutePlanner(createHttpRoutePlanner(builder, fileSystemOptions))
211                .setConnectionManager(createConnectionManager(builder, fileSystemOptions, sslContext, hostNameVerifier))
212                .setSSLContext(sslContext)
213                .setSSLHostnameVerifier(hostNameVerifier)
214                .setConnectionReuseStrategy(connectionReuseStrategy)
215                .setDefaultRequestConfig(createDefaultRequestConfig(builder, fileSystemOptions))
216                .setDefaultHeaders(defaultHeaders)
217                .setDefaultCookieStore(createDefaultCookieStore(builder, fileSystemOptions));
218
219        if (!builder.getFollowRedirect(fileSystemOptions)) {
220            httpClientBuilder.disableRedirectHandling();
221        }
222
223        return httpClientBuilder;
224    }
225
226    /**
227     * Create an {@link HttpClientContext} object for an http4 file system.
228     *
229     * @param builder Configuration options builder for http4 provider
230     * @param rootName The root path
231     * @param fileSystemOptions The FileSystem options
232     * @param authData The {@code UserAuthenticationData} object
233     * @return an {@link HttpClientContext} object
234     */
235    protected HttpClientContext createHttpClientContext(final Http4FileSystemConfigBuilder builder,
236            final GenericFileName rootName, final FileSystemOptions fileSystemOptions,
237            final UserAuthenticationData authData) {
238
239        final HttpClientContext clientContext = HttpClientContext.create();
240        final CredentialsProvider credsProvider = new BasicCredentialsProvider();
241        clientContext.setCredentialsProvider(credsProvider);
242
243        final String username = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
244                UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName())));
245        final String password = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
246                UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword())));
247
248        if (!StringUtils.isEmpty(username)) {
249            credsProvider.setCredentials(new AuthScope(rootName.getHostName(), rootName.getPort()),
250                    new UsernamePasswordCredentials(username, password));
251        }
252
253        final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
254
255        if (proxyHost != null) {
256            final UserAuthenticator proxyAuth = builder.getProxyAuthenticator(fileSystemOptions);
257
258            if (proxyAuth != null) {
259                final UserAuthenticationData proxyAuthData = UserAuthenticatorUtils.authenticate(proxyAuth,
260                    new UserAuthenticationData.Type[] {UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
261
262                if (proxyAuthData != null) {
263                    final UsernamePasswordCredentials proxyCreds = new UsernamePasswordCredentials(
264                            UserAuthenticatorUtils.toString(
265                                    UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, null)),
266                            UserAuthenticatorUtils.toString(
267                                    UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.PASSWORD, null)));
268
269                    credsProvider.setCredentials(new AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
270                            proxyCreds);
271                }
272
273                if (builder.isPreemptiveAuth(fileSystemOptions)) {
274                    final AuthCache authCache = new BasicAuthCache();
275                    final BasicScheme basicAuth = new BasicScheme();
276                    authCache.put(proxyHost, basicAuth);
277                    clientContext.setAuthCache(authCache);
278                }
279            }
280        }
281
282        return clientContext;
283    }
284
285    private HttpRoutePlanner createHttpRoutePlanner(final Http4FileSystemConfigBuilder builder,
286            final FileSystemOptions fileSystemOptions) {
287        final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
288
289        if (proxyHost != null) {
290            return new DefaultProxyRoutePlanner(proxyHost);
291        }
292
293        return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
294    }
295
296    /**
297     * Create {@link SSLContext} for HttpClient. Invoked by {@link #createHttpClientBuilder(Http4FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
298     *
299     * @param builder Configuration options builder for HTTP4 provider
300     * @param fileSystemOptions The FileSystem options
301     * @return a {@link SSLContext} for HttpClient
302     * @throws FileSystemException if an error occurs
303     */
304    protected SSLContext createSSLContext(final Http4FileSystemConfigBuilder builder,
305            final FileSystemOptions fileSystemOptions) throws FileSystemException {
306        try {
307            final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
308            sslContextBuilder.setKeyStoreType(builder.getKeyStoreType(fileSystemOptions));
309
310            File keystoreFileObject = null;
311            final String keystoreFile = builder.getKeyStoreFile(fileSystemOptions);
312
313            if (!StringUtils.isEmpty(keystoreFile)) {
314                keystoreFileObject = new File(keystoreFile);
315            }
316
317            if (keystoreFileObject != null && keystoreFileObject.exists()) {
318                final String keystorePass = builder.getKeyStorePass(fileSystemOptions);
319                final char[] keystorePassChars = keystorePass != null ? keystorePass.toCharArray() : null;
320                sslContextBuilder.loadTrustMaterial(keystoreFileObject, keystorePassChars, TrustAllStrategy.INSTANCE);
321            } else {
322                sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE);
323            }
324
325            return sslContextBuilder.build();
326        } catch (final KeyStoreException e) {
327            throw new FileSystemException("Keystore error. " + e.getMessage(), e);
328        } catch (final KeyManagementException e) {
329            throw new FileSystemException("Cannot retrieve keys. " + e.getMessage(), e);
330        } catch (final NoSuchAlgorithmException e) {
331            throw new FileSystemException("Algorithm error. " + e.getMessage(), e);
332        } catch (final CertificateException e) {
333            throw new FileSystemException("Certificate error. " + e.getMessage(), e);
334        } catch (final IOException e) {
335            throw new FileSystemException("Cannot open key file. " + e.getMessage(), e);
336        }
337    }
338
339    @Override
340    protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions)
341            throws FileSystemException {
342        final GenericFileName rootName = (GenericFileName) name;
343        UserAuthenticationData authData = null;
344        HttpClient httpClient;
345        HttpClientContext httpClientContext;
346        try {
347            final Http4FileSystemConfigBuilder builder = Http4FileSystemConfigBuilder.getInstance();
348            authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES);
349            httpClientContext = createHttpClientContext(builder, rootName, fileSystemOptions, authData);
350            httpClient = createHttpClient(builder, rootName, fileSystemOptions);
351        } finally {
352            UserAuthenticatorUtils.cleanup(authData);
353        }
354        return new Http4FileSystem(rootName, fileSystemOptions, httpClient, httpClientContext);
355    }
356
357    @Override
358    public Collection<Capability> getCapabilities() {
359        return CAPABILITIES;
360    }
361
362    @Override
363    public FileSystemConfigBuilder getConfigBuilder() {
364        return Http4FileSystemConfigBuilder.getInstance();
365    }
366
367    private HttpHost getProxyHttpHost(final Http4FileSystemConfigBuilder builder,
368            final FileSystemOptions fileSystemOptions) {
369        final String proxyHost = builder.getProxyHost(fileSystemOptions);
370        final int proxyPort = builder.getProxyPort(fileSystemOptions);
371        final String proxyScheme = builder.getProxyScheme(fileSystemOptions);
372
373        if (!StringUtils.isEmpty(proxyHost) && proxyPort > 0) {
374            return new HttpHost(proxyHost, proxyPort, proxyScheme);
375        }
376
377        return null;
378    }
379
380}