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