1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Objects;
32 import java.util.stream.Stream;
33
34 import javax.net.ssl.HostnameVerifier;
35 import javax.net.ssl.SSLContext;
36
37 import org.apache.commons.lang3.StringUtils;
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.hc.client5.http.auth.AuthCache;
50 import org.apache.hc.client5.http.auth.AuthScope;
51 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
52 import org.apache.hc.client5.http.classic.HttpClient;
53 import org.apache.hc.client5.http.config.ConnectionConfig;
54 import org.apache.hc.client5.http.cookie.BasicCookieStore;
55 import org.apache.hc.client5.http.cookie.Cookie;
56 import org.apache.hc.client5.http.cookie.CookieStore;
57 import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
58 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
59 import org.apache.hc.client5.http.impl.auth.BasicScheme;
60 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
61 import org.apache.hc.client5.http.impl.classic.HttpClients;
62 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
63 import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
64 import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
65 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
66 import org.apache.hc.client5.http.protocol.HttpClientContext;
67 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
68 import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier;
69 import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
70 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
71 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
72 import org.apache.hc.client5.http.ssl.TrustAllStrategy;
73 import org.apache.hc.core5.http.ConnectionReuseStrategy;
74 import org.apache.hc.core5.http.Header;
75 import org.apache.hc.core5.http.HttpHeaders;
76 import org.apache.hc.core5.http.HttpHost;
77 import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
78 import org.apache.hc.core5.http.io.SocketConfig;
79 import org.apache.hc.core5.http.message.BasicHeader;
80 import org.apache.hc.core5.http.ssl.TLS;
81 import org.apache.hc.core5.ssl.SSLContextBuilder;
82 import org.apache.hc.core5.util.Timeout;
83
84
85
86
87
88
89 public class Http5FileProvider extends AbstractOriginatingFileProvider {
90
91
92 static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES =
93 {
94 UserAuthenticationData.USERNAME,
95 UserAuthenticationData.PASSWORD
96 };
97
98
99 static final Collection<Capability> CAPABILITIES =
100 Collections.unmodifiableCollection(
101 Arrays.asList(
102 Capability.GET_TYPE,
103 Capability.READ_CONTENT,
104 Capability.URI,
105 Capability.GET_LAST_MODIFIED,
106 Capability.ATTRIBUTES,
107 Capability.RANDOM_ACCESS_READ,
108 Capability.DIRECTORY_READ_CONTENT
109 )
110 );
111
112
113
114
115 public Http5FileProvider() {
116 setFileNameParser(Http5FileNameParser.getInstance());
117 }
118
119 private HttpClientConnectionManager createConnectionManager(final Http5FileSystemConfigBuilder builder,
120 final FileSystemOptions fileSystemOptions) throws FileSystemException {
121
122 final ConnectionConfig connectionConfig = ConnectionConfig.custom()
123 .setConnectTimeout(Timeout.of(builder.getSoTimeoutDuration(fileSystemOptions)))
124 .build();
125
126 final SocketConfig socketConfig =
127 SocketConfig
128 .custom()
129 .setSoTimeout(Timeout.of(builder.getSoTimeoutDuration(fileSystemOptions)))
130 .build();
131
132 final String[] tlsVersions = builder.getTlsVersions(fileSystemOptions).split("\\s*,\\s*");
133
134 final TLS[] tlsArray = Stream.of(tlsVersions).filter(Objects::nonNull).map(TLS::valueOf).toArray(TLS[]::new);
135
136 final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
137 .setSslContext(createSSLContext(builder, fileSystemOptions))
138 .setHostnameVerifier(createHostnameVerifier(builder, fileSystemOptions))
139 .setTlsVersions(tlsArray)
140 .build();
141
142 return PoolingHttpClientConnectionManagerBuilder.create()
143 .setDefaultConnectionConfig(connectionConfig)
144 .setSSLSocketFactory(sslSocketFactory)
145 .setMaxConnTotal(builder.getMaxTotalConnections(fileSystemOptions))
146 .setMaxConnPerRoute(builder.getMaxConnectionsPerHost(fileSystemOptions))
147 .setDefaultSocketConfig(socketConfig)
148 .build();
149 }
150
151 private CookieStore createDefaultCookieStore(final Http5FileSystemConfigBuilder builder,
152 final FileSystemOptions fileSystemOptions) {
153 final CookieStore cookieStore = new BasicCookieStore();
154 final Cookie[] cookies = builder.getCookies(fileSystemOptions);
155
156 if (cookies != null) {
157 Stream.of(cookies).forEach(cookieStore::addCookie);
158 }
159
160 return cookieStore;
161 }
162
163 private HostnameVerifier createHostnameVerifier(final Http5FileSystemConfigBuilder builder, final FileSystemOptions fileSystemOptions) {
164 if (!builder.isHostnameVerificationEnabled(fileSystemOptions)) {
165 return NoopHostnameVerifier.INSTANCE;
166 }
167 return new DefaultHostnameVerifier();
168 }
169
170
171
172
173
174
175
176
177
178
179 protected HttpClient createHttpClient(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName,
180 final FileSystemOptions fileSystemOptions) throws FileSystemException {
181 return createHttpClientBuilder(builder, rootName, fileSystemOptions).build();
182 }
183
184
185
186
187
188
189
190
191
192
193 protected HttpClientBuilder createHttpClientBuilder(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName,
194 final FileSystemOptions fileSystemOptions) throws FileSystemException {
195 final List<Header> defaultHeaders = new ArrayList<>();
196 defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, builder.getUserAgent(fileSystemOptions)));
197
198 final ConnectionReuseStrategy connectionReuseStrategy = builder.isKeepAlive(fileSystemOptions)
199 ? DefaultConnectionReuseStrategy.INSTANCE
200 : (request, response, context) -> false;
201
202 final HttpClientBuilder httpClientBuilder =
203 HttpClients.custom()
204 .setRoutePlanner(createHttpRoutePlanner(builder, fileSystemOptions))
205 .setConnectionManager(createConnectionManager(builder, fileSystemOptions))
206 .setConnectionReuseStrategy(connectionReuseStrategy)
207 .setDefaultHeaders(defaultHeaders)
208 .setDefaultCookieStore(createDefaultCookieStore(builder, fileSystemOptions));
209
210 if (!builder.getFollowRedirect(fileSystemOptions)) {
211 httpClientBuilder.disableRedirectHandling();
212 }
213
214 return httpClientBuilder;
215 }
216
217
218
219
220
221
222
223
224
225
226 protected HttpClientContext createHttpClientContext(final Http5FileSystemConfigBuilder builder,
227 final GenericFileName rootName, final FileSystemOptions fileSystemOptions,
228 final UserAuthenticationData authData) {
229
230 final HttpClientContext clientContext = HttpClientContext.create();
231 final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
232 clientContext.setCredentialsProvider(credsProvider);
233
234 final String username = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
235 UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName())));
236 final char[] password = UserAuthenticatorUtils.getData(authData,
237 UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword()));
238
239 if (!StringUtils.isEmpty(username)) {
240
241 credsProvider.setCredentials(new AuthScope(rootName.getHostName(), rootName.getPort()),
242 new UsernamePasswordCredentials(username, password));
243 }
244
245 final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
246
247 if (proxyHost != null) {
248 final UserAuthenticator proxyAuth = builder.getProxyAuthenticator(fileSystemOptions);
249
250 if (proxyAuth != null) {
251 final UserAuthenticationData proxyAuthData = UserAuthenticatorUtils.authenticate(proxyAuth,
252 new UserAuthenticationData.Type[] {UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD});
253
254 if (proxyAuthData != null) {
255 final UsernamePasswordCredentials proxyCreds = new UsernamePasswordCredentials(
256 UserAuthenticatorUtils.toString(
257 UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.USERNAME, null)),
258 UserAuthenticatorUtils.getData(proxyAuthData, UserAuthenticationData.PASSWORD, null));
259
260
261 credsProvider.setCredentials(new AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
262 proxyCreds);
263 }
264
265 if (builder.isPreemptiveAuth(fileSystemOptions)) {
266 final AuthCache authCache = new BasicAuthCache();
267 final BasicScheme basicAuth = new BasicScheme();
268 authCache.put(proxyHost, basicAuth);
269 clientContext.setAuthCache(authCache);
270 }
271 }
272 }
273
274 return clientContext;
275 }
276
277 private HttpRoutePlanner createHttpRoutePlanner(final Http5FileSystemConfigBuilder builder,
278 final FileSystemOptions fileSystemOptions) {
279 final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions);
280
281 if (proxyHost != null) {
282 return new DefaultProxyRoutePlanner(proxyHost);
283 }
284
285 return new SystemDefaultRoutePlanner(ProxySelector.getDefault());
286 }
287
288
289
290
291
292
293
294
295
296 protected SSLContext createSSLContext(final Http5FileSystemConfigBuilder builder,
297 final FileSystemOptions fileSystemOptions) throws FileSystemException {
298 try {
299 final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
300 sslContextBuilder.setKeyStoreType(builder.getKeyStoreType(fileSystemOptions));
301
302 File keystoreFileObject = null;
303 final String keystoreFile = builder.getKeyStoreFile(fileSystemOptions);
304
305 if (!StringUtils.isEmpty(keystoreFile)) {
306 keystoreFileObject = new File(keystoreFile);
307 }
308
309 if (keystoreFileObject != null && keystoreFileObject.exists()) {
310 final String keystorePass = builder.getKeyStorePass(fileSystemOptions);
311 final char[] keystorePassChars = keystorePass != null ? keystorePass.toCharArray() : null;
312 sslContextBuilder.loadTrustMaterial(keystoreFileObject, keystorePassChars, TrustAllStrategy.INSTANCE);
313 } else {
314 sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE);
315 }
316
317 return sslContextBuilder.build();
318 } catch (final KeyStoreException e) {
319 throw new FileSystemException("Keystore error. " + e.getMessage(), e);
320 } catch (final KeyManagementException e) {
321 throw new FileSystemException("Cannot retrieve keys. " + e.getMessage(), e);
322 } catch (final NoSuchAlgorithmException e) {
323 throw new FileSystemException("Algorithm error. " + e.getMessage(), e);
324 } catch (final CertificateException e) {
325 throw new FileSystemException("Certificate error. " + e.getMessage(), e);
326 } catch (final IOException e) {
327 throw new FileSystemException("Cannot open key file. " + e.getMessage(), e);
328 }
329 }
330
331 @Override
332 protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions)
333 throws FileSystemException {
334 final GenericFileName rootName = (GenericFileName) name;
335 UserAuthenticationData authData = null;
336 HttpClient httpClient;
337 HttpClientContext httpClientContext;
338 try {
339 final Http5FileSystemConfigBuilder builder = Http5FileSystemConfigBuilder.getInstance();
340 authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES);
341 httpClientContext = createHttpClientContext(builder, rootName, fileSystemOptions, authData);
342 httpClient = createHttpClient(builder, rootName, fileSystemOptions);
343 } finally {
344 UserAuthenticatorUtils.cleanup(authData);
345 }
346 return new Http5FileSystem(rootName, fileSystemOptions, httpClient, httpClientContext);
347 }
348
349 @Override
350 public Collection<Capability> getCapabilities() {
351 return CAPABILITIES;
352 }
353
354 @Override
355 public FileSystemConfigBuilder getConfigBuilder() {
356 return Http5FileSystemConfigBuilder.getInstance();
357 }
358
359 private HttpHost getProxyHttpHost(final Http5FileSystemConfigBuilder builder,
360 final FileSystemOptions fileSystemOptions) {
361 final String proxyScheme = builder.getProxyScheme(fileSystemOptions);
362 final String proxyHost = builder.getProxyHost(fileSystemOptions);
363 final int proxyPort = builder.getProxyPort(fileSystemOptions);
364
365 if (!StringUtils.isEmpty(proxyHost) && proxyPort > 0) {
366 return new HttpHost(proxyScheme, proxyHost, proxyPort);
367 }
368
369 return null;
370 }
371
372 }