View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.http5;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.net.ProxySelector;
22  import java.security.KeyManagementException;
23  import java.security.KeyStoreException;
24  import java.security.NoSuchAlgorithmException;
25  import java.security.cert.CertificateException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.stream.Stream;
32  
33  import javax.net.ssl.HostnameVerifier;
34  import javax.net.ssl.SSLContext;
35  
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.commons.vfs2.Capability;
38  import org.apache.commons.vfs2.FileName;
39  import org.apache.commons.vfs2.FileSystem;
40  import org.apache.commons.vfs2.FileSystemConfigBuilder;
41  import org.apache.commons.vfs2.FileSystemException;
42  import org.apache.commons.vfs2.FileSystemOptions;
43  import org.apache.commons.vfs2.UserAuthenticationData;
44  import org.apache.commons.vfs2.UserAuthenticator;
45  import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider;
46  import org.apache.commons.vfs2.provider.GenericFileName;
47  import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
48  import org.apache.hc.client5.http.auth.AuthCache;
49  import org.apache.hc.client5.http.auth.AuthScope;
50  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
51  import org.apache.hc.client5.http.classic.HttpClient;
52  import org.apache.hc.client5.http.config.RequestConfig;
53  import org.apache.hc.client5.http.cookie.BasicCookieStore;
54  import org.apache.hc.client5.http.cookie.Cookie;
55  import org.apache.hc.client5.http.cookie.CookieStore;
56  import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
57  import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
58  import org.apache.hc.client5.http.impl.auth.BasicScheme;
59  import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
60  import org.apache.hc.client5.http.impl.classic.HttpClients;
61  import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
62  import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
63  import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
64  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
65  import org.apache.hc.client5.http.protocol.HttpClientContext;
66  import org.apache.hc.client5.http.routing.HttpRoutePlanner;
67  import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
68  import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
69  import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
70  import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
71  import org.apache.hc.client5.http.ssl.TrustAllStrategy;
72  import org.apache.hc.core5.http.ConnectionReuseStrategy;
73  import org.apache.hc.core5.http.Header;
74  import org.apache.hc.core5.http.HttpHeaders;
75  import org.apache.hc.core5.http.HttpHost;
76  import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
77  import org.apache.hc.core5.http.io.SocketConfig;
78  import org.apache.hc.core5.http.message.BasicHeader;
79  import org.apache.hc.core5.http.ssl.TLS;
80  import org.apache.hc.core5.ssl.SSLContextBuilder;
81  import org.apache.hc.core5.util.Timeout;
82  
83  /**
84   * {@code FileProvider} implementation using HttpComponents HttpClient v5 library.
85   *
86   * @since 2.5.0
87   */
88  public class Http5FileProvider extends AbstractOriginatingFileProvider {
89  
90      /** Authenticator information. */
91      static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES =
92              new UserAuthenticationData.Type[] {
93                      UserAuthenticationData.USERNAME,
94                      UserAuthenticationData.PASSWORD
95                      };
96  
97      /** FileProvider capabilities */
98      static final Collection<Capability> CAPABILITIES =
99              Collections.unmodifiableCollection(
100                     Arrays.asList(
101                             Capability.GET_TYPE,
102                             Capability.READ_CONTENT,
103                             Capability.URI,
104                             Capability.GET_LAST_MODIFIED,
105                             Capability.ATTRIBUTES,
106                             Capability.RANDOM_ACCESS_READ,
107                             Capability.DIRECTORY_READ_CONTENT
108                             )
109                     );
110 
111     /**
112      * Constructs a new provider.
113      */
114     public Http5FileProvider() {
115         setFileNameParser(Http5FileNameParser.getInstance());
116     }
117 
118     private HttpClientConnectionManager createConnectionManager(final Http5FileSystemConfigBuilder builder,
119             final FileSystemOptions fileSystemOptions) throws FileSystemException {
120 
121         final SocketConfig socketConfig =
122                 SocketConfig
123                 .custom()
124                 .setSoTimeout(Timeout.ofMilliseconds(builder.getSoTimeoutDuration(fileSystemOptions).toMillis()))
125                 .build();
126 
127         final String[] tlsVersions = builder.getTlsVersions(fileSystemOptions).split("\\s*,\\s*");
128 
129         final TLS[] tlsArray = Stream.of(tlsVersions).map(TLS::valueOf).toArray(TLS[]::new);
130 
131         final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
132                 .setSslContext(createSSLContext(builder, fileSystemOptions))
133                 .setHostnameVerifier(createHostnameVerifier(builder, fileSystemOptions))
134                 .setTlsVersions(tlsArray)
135                 .build();
136 
137         return PoolingHttpClientConnectionManagerBuilder.create()
138                 .setSSLSocketFactory(sslSocketFactory)
139                 .setMaxConnTotal(builder.getMaxTotalConnections(fileSystemOptions))
140                 .setMaxConnPerRoute(builder.getMaxConnectionsPerHost(fileSystemOptions))
141                 .setDefaultSocketConfig(socketConfig)
142                 .build();
143     }
144 
145     private CookieStore createDefaultCookieStore(final Http5FileSystemConfigBuilder builder,
146             final FileSystemOptions fileSystemOptions) {
147         final CookieStore cookieStore = new BasicCookieStore();
148         final Cookie[] cookies = builder.getCookies(fileSystemOptions);
149 
150         if (cookies != null) {
151             Stream.of(cookies).forEach(cookieStore::addCookie);
152         }
153 
154         return cookieStore;
155     }
156 
157     private RequestConfig createDefaultRequestConfig(final Http5FileSystemConfigBuilder builder,
158             final FileSystemOptions fileSystemOptions) {
159         return RequestConfig.custom()
160                 .setConnectTimeout(Timeout.ofMilliseconds(builder.getSoTimeoutDuration(fileSystemOptions).toMillis()))
161                 .build();
162     }
163 
164     private HostnameVerifier createHostnameVerifier(final Http5FileSystemConfigBuilder builder, final FileSystemOptions fileSystemOptions) {
165         if (!builder.isHostnameVerificationEnabled(fileSystemOptions)) {
166             return NoopHostnameVerifier.INSTANCE;
167         }
168         return new DefaultHostnameVerifier();
169     }
170 
171     /**
172      * Create an {@link HttpClient} object for an http4 file system.
173      *
174      * @param builder Configuration options builder for http4 provider
175      * @param rootName The root path
176      * @param fileSystemOptions The file system options
177      * @return an {@link HttpClient} object
178      * @throws FileSystemException if an error occurs.
179      */
180     protected HttpClient createHttpClient(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName,
181             final FileSystemOptions fileSystemOptions) throws FileSystemException {
182         return createHttpClientBuilder(builder, rootName, fileSystemOptions).build();
183     }
184 
185     /**
186      * Create an {@link HttpClientBuilder} object. Invoked by {@link #createHttpClient(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
187      *
188      * @param builder Configuration options builder for HTTP4 provider
189      * @param rootName The root path
190      * @param fileSystemOptions The FileSystem options
191      * @return an {@link HttpClientBuilder} object
192      * @throws FileSystemException if an error occurs
193      */
194     protected HttpClientBuilder createHttpClientBuilder(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName,
195             final FileSystemOptions fileSystemOptions) throws FileSystemException {
196         final List<Header> defaultHeaders = new ArrayList<>();
197         defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, builder.getUserAgent(fileSystemOptions)));
198 
199         final ConnectionReuseStrategy connectionReuseStrategy = builder.isKeepAlive(fileSystemOptions)
200                 ? DefaultConnectionReuseStrategy.INSTANCE
201                 : (request, response, context) -> false;
202 
203         final HttpClientBuilder httpClientBuilder =
204                 HttpClients.custom()
205                 .setRoutePlanner(createHttpRoutePlanner(builder, fileSystemOptions))
206                 .setConnectionManager(createConnectionManager(builder, fileSystemOptions))
207                 .setConnectionReuseStrategy(connectionReuseStrategy)
208                 .setDefaultRequestConfig(createDefaultRequestConfig(builder, fileSystemOptions))
209                 .setDefaultHeaders(defaultHeaders)
210                 .setDefaultCookieStore(createDefaultCookieStore(builder, fileSystemOptions));
211 
212         if (!builder.getFollowRedirect(fileSystemOptions)) {
213             httpClientBuilder.disableRedirectHandling();
214         }
215 
216         return httpClientBuilder;
217     }
218 
219     /**
220      * Create an {@link HttpClientContext} object for an http4 file system.
221      *
222      * @param builder Configuration options builder for http4 provider
223      * @param rootName The root path
224      * @param fileSystemOptions The FileSystem options
225      * @param authData The {@code UserAuthentiationData} object
226      * @return an {@link HttpClientContext} object
227      */
228     protected HttpClientContext createHttpClientContext(final Http5FileSystemConfigBuilder builder,
229             final GenericFileName rootName, final FileSystemOptions fileSystemOptions,
230             final UserAuthenticationData authData) {
231 
232         final HttpClientContext clientContext = HttpClientContext.create();
233         final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
234         clientContext.setCredentialsProvider(credsProvider);
235 
236         final String username = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
237                 UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName())));
238         final char[] password = UserAuthenticatorUtils.getData(authData,
239                 UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword()));
240 
241         if (!StringUtils.isEmpty(username)) {
242             // set root port
243             credsProvider.setCredentials(new AuthScope(rootName.getHostName(), rootName.getPort()),
244                     new UsernamePasswordCredentials(username, password));
245         }
246 
247         final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
248 
249         if (proxyHost != null) {
250             final UserAuthenticator proxyAuth = builder.getProxyAuthenticator(fileSystemOptions);
251 
252             if (proxyAuth != null) {
253                 final UserAuthenticationData proxyAuthData = UserAuthenticatorUtils.authenticate(proxyAuth,
254                         new UserAuthenticationData.Type[] { UserAuthenticationData.USERNAME,
255                                 UserAuthenticationData.PASSWORD });
256 
257                 if (proxyAuthData != null) {
258                     final UsernamePasswordCredentials proxyCreds = new UsernamePasswordCredentials(
259                             UserAuthenticatorUtils.toString(
260                                     UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, null)),
261                             UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.PASSWORD, null));
262 
263                     // set proxy host port
264                     credsProvider.setCredentials(new AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
265                             proxyCreds);
266                 }
267 
268                 if (builder.isPreemptiveAuth(fileSystemOptions)) {
269                     final AuthCache authCache = new BasicAuthCache();
270                     final BasicScheme basicAuth = new BasicScheme();
271                     authCache.put(proxyHost, basicAuth);
272                     clientContext.setAuthCache(authCache);
273                 }
274             }
275         }
276 
277         return clientContext;
278     }
279 
280     private HttpRoutePlanner createHttpRoutePlanner(final Http5FileSystemConfigBuilder builder,
281             final FileSystemOptions fileSystemOptions) {
282         final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
283 
284         if (proxyHost != null) {
285             return new DefaultProxyRoutePlanner(proxyHost);
286         }
287 
288         return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
289     }
290 
291     /**
292      * Create {@link SSLContext} for HttpClient. Invoked by {@link #createHttpClientBuilder(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
293      *
294      * @param builder Configuration options builder for HTTP4 provider
295      * @param fileSystemOptions The FileSystem options
296      * @return a {@link SSLContext} for HttpClient
297      * @throws FileSystemException if an error occurs
298      */
299     protected SSLContext createSSLContext(final Http5FileSystemConfigBuilder builder,
300             final FileSystemOptions fileSystemOptions) throws FileSystemException {
301         try {
302             final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
303             sslContextBuilder.setKeyStoreType(builder.getKeyStoreType(fileSystemOptions));
304 
305             File keystoreFileObject = null;
306             final String keystoreFile = builder.getKeyStoreFile(fileSystemOptions);
307 
308             if (!StringUtils.isEmpty(keystoreFile)) {
309                 keystoreFileObject = new File(keystoreFile);
310             }
311 
312             if (keystoreFileObject != null && keystoreFileObject.exists()) {
313                 final String keystorePass = builder.getKeyStorePass(fileSystemOptions);
314                 final char[] keystorePassChars = (keystorePass != null) ? keystorePass.toCharArray() : null;
315                 sslContextBuilder.loadTrustMaterial(keystoreFileObject, keystorePassChars, TrustAllStrategy.INSTANCE);
316             } else {
317                 sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE);
318             }
319 
320             return sslContextBuilder.build();
321         } catch (final KeyStoreException e) {
322             throw new FileSystemException("Keystore error. " + e.getMessage(), e);
323         } catch (final KeyManagementException e) {
324             throw new FileSystemException("Cannot retrieve keys. " + e.getMessage(), e);
325         } catch (final NoSuchAlgorithmException e) {
326             throw new FileSystemException("Algorithm error. " + e.getMessage(), e);
327         } catch (final CertificateException e) {
328             throw new FileSystemException("Certificate error. " + e.getMessage(), e);
329         } catch (final IOException e) {
330             throw new FileSystemException("Cannot open key file. " + e.getMessage(), e);
331         }
332     }
333 
334     @Override
335     protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions)
336             throws FileSystemException {
337         final GenericFileName/../../org/apache/commons/vfs2/provider/GenericFileName.html#GenericFileName">GenericFileName rootName = (GenericFileName) name;
338 
339         UserAuthenticationData authData = null;
340         HttpClient httpClient;
341         HttpClientContext httpClientContext;
342 
343         try {
344             final Http5FileSystemConfigBuilder builder = Http5FileSystemConfigBuilder.getInstance();
345             authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES);
346             httpClientContext = createHttpClientContext(builder, rootName, fileSystemOptions, authData);
347             httpClient = createHttpClient(builder, rootName, fileSystemOptions);
348         } finally {
349             UserAuthenticatorUtils.cleanup(authData);
350         }
351 
352         return new Http5FileSystem(rootName, fileSystemOptions, httpClient, httpClientContext);
353     }
354 
355     @Override
356     public Collection<Capability> getCapabilities() {
357         return CAPABILITIES;
358     }
359 
360     @Override
361     public FileSystemConfigBuilder getConfigBuilder() {
362         return Http5FileSystemConfigBuilder.getInstance();
363     }
364 
365     private HttpHost getProxyHttpHost(final Http5FileSystemConfigBuilder builder,
366             final FileSystemOptions fileSystemOptions) {
367         final String proxyScheme = builder.getProxyScheme(fileSystemOptions);
368         final String proxyHost = builder.getProxyHost(fileSystemOptions);
369         final int proxyPort = builder.getProxyPort(fileSystemOptions);
370 
371         if (!StringUtils.isEmpty(proxyHost) && proxyPort > 0) {
372             return new HttpHost(proxyScheme, proxyHost, proxyPort);
373         }
374 
375         return null;
376     }
377 
378 }