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.http4;
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.lang3.time.DurationUtils;
38  import org.apache.commons.vfs2.Capability;
39  import org.apache.commons.vfs2.FileName;
40  import org.apache.commons.vfs2.FileSystem;
41  import org.apache.commons.vfs2.FileSystemConfigBuilder;
42  import org.apache.commons.vfs2.FileSystemException;
43  import org.apache.commons.vfs2.FileSystemOptions;
44  import org.apache.commons.vfs2.UserAuthenticationData;
45  import org.apache.commons.vfs2.UserAuthenticator;
46  import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider;
47  import org.apache.commons.vfs2.provider.GenericFileName;
48  import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
49  import org.apache.http.ConnectionReuseStrategy;
50  import org.apache.http.Header;
51  import org.apache.http.HttpHost;
52  import org.apache.http.auth.AuthScope;
53  import org.apache.http.auth.UsernamePasswordCredentials;
54  import org.apache.http.client.AuthCache;
55  import org.apache.http.client.CookieStore;
56  import org.apache.http.client.CredentialsProvider;
57  import org.apache.http.client.HttpClient;
58  import org.apache.http.client.config.RequestConfig;
59  import org.apache.http.client.protocol.HttpClientContext;
60  import org.apache.http.config.Registry;
61  import org.apache.http.config.RegistryBuilder;
62  import org.apache.http.config.SocketConfig;
63  import org.apache.http.conn.HttpClientConnectionManager;
64  import org.apache.http.conn.routing.HttpRoutePlanner;
65  import org.apache.http.conn.socket.ConnectionSocketFactory;
66  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
67  import org.apache.http.conn.ssl.DefaultHostnameVerifier;
68  import org.apache.http.conn.ssl.NoopHostnameVerifier;
69  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
70  import org.apache.http.conn.ssl.TrustAllStrategy;
71  import org.apache.http.cookie.Cookie;
72  import org.apache.http.impl.DefaultConnectionReuseStrategy;
73  import org.apache.http.impl.NoConnectionReuseStrategy;
74  import org.apache.http.impl.auth.BasicScheme;
75  import org.apache.http.impl.client.BasicAuthCache;
76  import org.apache.http.impl.client.BasicCookieStore;
77  import org.apache.http.impl.client.BasicCredentialsProvider;
78  import org.apache.http.impl.client.HttpClientBuilder;
79  import org.apache.http.impl.client.HttpClients;
80  import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
81  import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
82  import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
83  import org.apache.http.message.BasicHeader;
84  import org.apache.http.protocol.HTTP;
85  import org.apache.http.ssl.SSLContextBuilder;
86  
87  /**
88   * {@code FileProvider} implementation using HttpComponents HttpClient library.
89   *
90   * @since 2.3
91   */
92  public class Http4FileProvider extends AbstractOriginatingFileProvider {
93  
94      /** Authenticator information. */
95      static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES =
96              new UserAuthenticationData.Type[] {
97                      UserAuthenticationData.USERNAME,
98                      UserAuthenticationData.PASSWORD
99                      };
100 
101     /** FileProvider capabilities */
102     static final Collection<Capability> CAPABILITIES =
103             Collections.unmodifiableCollection(
104                     Arrays.asList(
105                             Capability.GET_TYPE,
106                             Capability.READ_CONTENT,
107                             Capability.URI,
108                             Capability.GET_LAST_MODIFIED,
109                             Capability.ATTRIBUTES,
110                             Capability.RANDOM_ACCESS_READ,
111                             Capability.DIRECTORY_READ_CONTENT
112                             )
113                     );
114 
115     /**
116      * Constructs a new provider.
117      */
118     public Http4FileProvider() {
119         setFileNameParser(Http4FileNameParser.getInstance());
120     }
121 
122     private HttpClientConnectionManager createConnectionManager(final Http4FileSystemConfigBuilder builder,
123         final FileSystemOptions fileSystemOptions, final SSLContext sslContext, final HostnameVerifier verifier) {
124         final SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, verifier);
125         final Registry<ConnectionSocketFactory> socketFactoryRegistry =
126                 RegistryBuilder.<ConnectionSocketFactory> create()
127                         .register("https", sslFactory)
128                         .register("http", new PlainConnectionSocketFactory())
129                         .build();
130 
131         final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
132         connManager.setMaxTotal(builder.getMaxTotalConnections(fileSystemOptions));
133         connManager.setDefaultMaxPerRoute(builder.getMaxConnectionsPerHost(fileSystemOptions));
134 
135         // @formatter:off
136         final SocketConfig socketConfig =
137                 SocketConfig
138                 .custom()
139                 .setSoTimeout(DurationUtils.toMillisInt(builder.getSoTimeoutDuration(fileSystemOptions)))
140                 .build();
141         // @formatter:on
142 
143         connManager.setDefaultSocketConfig(socketConfig);
144 
145         return connManager;
146     }
147 
148     private CookieStore createDefaultCookieStore(final Http4FileSystemConfigBuilder builder,
149             final FileSystemOptions fileSystemOptions) {
150         final CookieStore cookieStore = new BasicCookieStore();
151         final Cookie[] cookies = builder.getCookies(fileSystemOptions);
152 
153         if (cookies != null) {
154             Stream.of(cookies).forEach(cookieStore::addCookie);
155         }
156 
157         return cookieStore;
158     }
159 
160     private RequestConfig createDefaultRequestConfig(final Http4FileSystemConfigBuilder builder,
161         final FileSystemOptions fileSystemOptions) {
162         return RequestConfig.custom()
163             .setConnectTimeout(DurationUtils.toMillisInt(builder.getConnectionTimeoutDuration(fileSystemOptions)))
164             .build();
165     }
166 
167     private HostnameVerifier createHostnameVerifier(final Http4FileSystemConfigBuilder builder,
168         final FileSystemOptions fileSystemOptions) {
169         return builder.isHostnameVerificationEnabled(fileSystemOptions) ? new DefaultHostnameVerifier()
170             : NoopHostnameVerifier.INSTANCE;
171     }
172 
173     /**
174      * Create an {@link HttpClient} object for an http4 file system.
175      *
176      * @param builder Configuration options builder for http4 provider
177      * @param rootName The root path
178      * @param fileSystemOptions The file system options
179      * @return an {@link HttpClient} object
180      * @throws FileSystemException if an error occurs.
181      */
182     protected HttpClient createHttpClient(final Http4FileSystemConfigBuilder builder, final GenericFileName rootName,
183             final FileSystemOptions fileSystemOptions) throws FileSystemException {
184         return createHttpClientBuilder(builder, rootName, fileSystemOptions).build();
185     }
186 
187     /**
188      * Create an {@link HttpClientBuilder} object. Invoked by {@link #createHttpClient(Http4FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
189      *
190      * @param builder Configuration options builder for HTTP4 provider
191      * @param rootName The root path
192      * @param fileSystemOptions The FileSystem options
193      * @return an {@link HttpClientBuilder} object
194      * @throws FileSystemException if an error occurs
195      */
196     protected HttpClientBuilder createHttpClientBuilder(final Http4FileSystemConfigBuilder builder, final GenericFileName rootName,
197             final FileSystemOptions fileSystemOptions) throws FileSystemException {
198         final List<Header> defaultHeaders = new ArrayList<>();
199         defaultHeaders.add(new BasicHeader(HTTP.USER_AGENT, builder.getUserAgent(fileSystemOptions)));
200 
201         final ConnectionReuseStrategy connectionReuseStrategy = builder.isKeepAlive(fileSystemOptions)
202                 ? DefaultConnectionReuseStrategy.INSTANCE
203                 : NoConnectionReuseStrategy.INSTANCE;
204         final SSLContext sslContext = createSSLContext(builder, fileSystemOptions);
205         final HostnameVerifier hostNameVerifier = createHostnameVerifier(builder, fileSystemOptions);
206         final HttpClientBuilder httpClientBuilder =
207                 HttpClients.custom()
208                 .setRoutePlanner(createHttpRoutePlanner(builder, fileSystemOptions))
209                 .setConnectionManager(createConnectionManager(builder, fileSystemOptions, sslContext, hostNameVerifier))
210                 .setSSLContext(sslContext)
211                 .setSSLHostnameVerifier(hostNameVerifier)
212                 .setConnectionReuseStrategy(connectionReuseStrategy)
213                 .setDefaultRequestConfig(createDefaultRequestConfig(builder, fileSystemOptions))
214                 .setDefaultHeaders(defaultHeaders)
215                 .setDefaultCookieStore(createDefaultCookieStore(builder, fileSystemOptions));
216 
217         if (!builder.getFollowRedirect(fileSystemOptions)) {
218             httpClientBuilder.disableRedirectHandling();
219         }
220 
221         return httpClientBuilder;
222     }
223 
224     /**
225      * Create an {@link HttpClientContext} object for an http4 file system.
226      *
227      * @param builder Configuration options builder for http4 provider
228      * @param rootName The root path
229      * @param fileSystemOptions The FileSystem options
230      * @param authData The {@code UserAuthentiationData} object
231      * @return an {@link HttpClientContext} object
232      */
233     protected HttpClientContext createHttpClientContext(final Http4FileSystemConfigBuilder builder,
234             final GenericFileName rootName, final FileSystemOptions fileSystemOptions,
235             final UserAuthenticationData authData) {
236 
237         final HttpClientContext clientContext = HttpClientContext.create();
238         final CredentialsProvider credsProvider = new BasicCredentialsProvider();
239         clientContext.setCredentialsProvider(credsProvider);
240 
241         final String username = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
242                 UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName())));
243         final String password = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
244                 UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword())));
245 
246         if (!StringUtils.isEmpty(username)) {
247             credsProvider.setCredentials(new AuthScope(rootName.getHostName(), rootName.getPort()),
248                     new UsernamePasswordCredentials(username, password));
249         }
250 
251         final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
252 
253         if (proxyHost != null) {
254             final UserAuthenticator proxyAuth = builder.getProxyAuthenticator(fileSystemOptions);
255 
256             if (proxyAuth != null) {
257                 final UserAuthenticationData proxyAuthData = UserAuthenticatorUtils.authenticate(proxyAuth,
258                         new UserAuthenticationData.Type[] { UserAuthenticationData.USERNAME,
259                                 UserAuthenticationData.PASSWORD });
260 
261                 if (proxyAuthData != null) {
262                     final UsernamePasswordCredentials proxyCreds = new UsernamePasswordCredentials(
263                             UserAuthenticatorUtils.toString(
264                                     UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, null)),
265                             UserAuthenticatorUtils.toString(
266                                     UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.PASSWORD, null)));
267 
268                     credsProvider.setCredentials(new AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
269                             proxyCreds);
270                 }
271 
272                 if (builder.isPreemptiveAuth(fileSystemOptions)) {
273                     final AuthCache authCache = new BasicAuthCache();
274                     final BasicScheme basicAuth = new BasicScheme();
275                     authCache.put(proxyHost, basicAuth);
276                     clientContext.setAuthCache(authCache);
277                 }
278             }
279         }
280 
281         return clientContext;
282     }
283 
284     private HttpRoutePlanner createHttpRoutePlanner(final Http4FileSystemConfigBuilder builder,
285             final FileSystemOptions fileSystemOptions) {
286         final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
287 
288         if (proxyHost != null) {
289             return new DefaultProxyRoutePlanner(proxyHost);
290         }
291 
292         return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
293     }
294 
295     /**
296      * Create {@link SSLContext} for HttpClient. Invoked by {@link #createHttpClientBuilder(Http4FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}.
297      *
298      * @param builder Configuration options builder for HTTP4 provider
299      * @param fileSystemOptions The FileSystem options
300      * @return a {@link SSLContext} for HttpClient
301      * @throws FileSystemException if an error occurs
302      */
303     protected SSLContext createSSLContext(final Http4FileSystemConfigBuilder builder,
304             final FileSystemOptions fileSystemOptions) throws FileSystemException {
305         try {
306             final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
307             sslContextBuilder.setKeyStoreType(builder.getKeyStoreType(fileSystemOptions));
308 
309             File keystoreFileObject = null;
310             final String keystoreFile = builder.getKeyStoreFile(fileSystemOptions);
311 
312             if (!StringUtils.isEmpty(keystoreFile)) {
313                 keystoreFileObject = new File(keystoreFile);
314             }
315 
316             if (keystoreFileObject != null && keystoreFileObject.exists()) {
317                 final String keystorePass = builder.getKeyStorePass(fileSystemOptions);
318                 final char[] keystorePassChars = (keystorePass != null) ? keystorePass.toCharArray() : null;
319                 sslContextBuilder.loadTrustMaterial(keystoreFileObject, keystorePassChars, TrustAllStrategy.INSTANCE);
320             } else {
321                 sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE);
322             }
323 
324             return sslContextBuilder.build();
325         } catch (final KeyStoreException e) {
326             throw new FileSystemException("Keystore error. " + e.getMessage(), e);
327         } catch (final KeyManagementException e) {
328             throw new FileSystemException("Cannot retrieve keys. " + e.getMessage(), e);
329         } catch (final NoSuchAlgorithmException e) {
330             throw new FileSystemException("Algorithm error. " + e.getMessage(), e);
331         } catch (final CertificateException e) {
332             throw new FileSystemException("Certificate error. " + e.getMessage(), e);
333         } catch (final IOException e) {
334             throw new FileSystemException("Cannot open key file. " + e.getMessage(), e);
335         }
336     }
337 
338     @Override
339     protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions)
340             throws FileSystemException {
341         final GenericFileName/../../org/apache/commons/vfs2/provider/GenericFileName.html#GenericFileName">GenericFileName rootName = (GenericFileName) name;
342 
343         UserAuthenticationData authData = null;
344         HttpClient httpClient;
345         HttpClientContext httpClientContext;
346 
347         try {
348             final Http4FileSystemConfigBuilder builder = Http4FileSystemConfigBuilder.getInstance();
349             authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES);
350             httpClientContext = createHttpClientContext(builder, rootName, fileSystemOptions, authData);
351             httpClient = createHttpClient(builder, rootName, fileSystemOptions);
352         } finally {
353             UserAuthenticatorUtils.cleanup(authData);
354         }
355 
356         return new Http4FileSystem(rootName, fileSystemOptions, httpClient, httpClientContext);
357     }
358 
359     @Override
360     public Collection<Capability> getCapabilities() {
361         return CAPABILITIES;
362     }
363 
364     @Override
365     public FileSystemConfigBuilder getConfigBuilder() {
366         return Http4FileSystemConfigBuilder.getInstance();
367     }
368 
369     private HttpHost getProxyHttpHost(final Http4FileSystemConfigBuilder builder,
370             final FileSystemOptions fileSystemOptions) {
371         final String proxyHost = builder.getProxyHost(fileSystemOptions);
372         final int proxyPort = builder.getProxyPort(fileSystemOptions);
373         final String proxyScheme = builder.getProxyScheme(fileSystemOptions);
374 
375         if (!StringUtils.isEmpty(proxyHost) && proxyPort > 0) {
376             return new HttpHost(proxyHost, proxyPort, proxyScheme);
377         }
378 
379         return null;
380     }
381 
382 }