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