001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.http5; 018 019import java.io.File; 020import java.io.IOException; 021import java.net.ProxySelector; 022import java.security.KeyManagementException; 023import java.security.KeyStoreException; 024import java.security.NoSuchAlgorithmException; 025import java.security.cert.CertificateException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.List; 031import java.util.Objects; 032import java.util.stream.Stream; 033 034import javax.net.ssl.HostnameVerifier; 035import javax.net.ssl.SSLContext; 036 037import org.apache.commons.lang3.StringUtils; 038import org.apache.commons.vfs2.Capability; 039import org.apache.commons.vfs2.FileName; 040import org.apache.commons.vfs2.FileSystem; 041import org.apache.commons.vfs2.FileSystemConfigBuilder; 042import org.apache.commons.vfs2.FileSystemException; 043import org.apache.commons.vfs2.FileSystemOptions; 044import org.apache.commons.vfs2.UserAuthenticationData; 045import org.apache.commons.vfs2.UserAuthenticator; 046import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider; 047import org.apache.commons.vfs2.provider.GenericFileName; 048import org.apache.commons.vfs2.util.UserAuthenticatorUtils; 049import org.apache.hc.client5.http.auth.AuthCache; 050import org.apache.hc.client5.http.auth.AuthScope; 051import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; 052import org.apache.hc.client5.http.classic.HttpClient; 053import org.apache.hc.client5.http.config.ConnectionConfig; 054import org.apache.hc.client5.http.cookie.BasicCookieStore; 055import org.apache.hc.client5.http.cookie.Cookie; 056import org.apache.hc.client5.http.cookie.CookieStore; 057import org.apache.hc.client5.http.impl.auth.BasicAuthCache; 058import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; 059import org.apache.hc.client5.http.impl.auth.BasicScheme; 060import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 061import org.apache.hc.client5.http.impl.classic.HttpClients; 062import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; 063import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; 064import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; 065import org.apache.hc.client5.http.io.HttpClientConnectionManager; 066import org.apache.hc.client5.http.protocol.HttpClientContext; 067import org.apache.hc.client5.http.routing.HttpRoutePlanner; 068import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; 069import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; 070import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; 071import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; 072import org.apache.hc.client5.http.ssl.TrustAllStrategy; 073import org.apache.hc.core5.http.ConnectionReuseStrategy; 074import org.apache.hc.core5.http.Header; 075import org.apache.hc.core5.http.HttpHeaders; 076import org.apache.hc.core5.http.HttpHost; 077import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; 078import org.apache.hc.core5.http.io.SocketConfig; 079import org.apache.hc.core5.http.message.BasicHeader; 080import org.apache.hc.core5.http.ssl.TLS; 081import org.apache.hc.core5.ssl.SSLContextBuilder; 082import org.apache.hc.core5.util.Timeout; 083 084/** 085 * {@code FileProvider} implementation using HttpComponents HttpClient v5 library. 086 * 087 * @since 2.5.0 088 */ 089public class Http5FileProvider extends AbstractOriginatingFileProvider { 090 091 /** Authenticator information. */ 092 static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES = 093 { 094 UserAuthenticationData.USERNAME, 095 UserAuthenticationData.PASSWORD 096 }; 097 098 /** FileProvider capabilities */ 099 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 * Constructs a new provider. 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 * Create an {@link HttpClient} object for an http4 file system. 172 * 173 * @param builder Configuration options builder for http4 provider 174 * @param rootName The root path 175 * @param fileSystemOptions The file system options 176 * @return an {@link HttpClient} object 177 * @throws FileSystemException if an error occurs. 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 * Create an {@link HttpClientBuilder} object. Invoked by {@link #createHttpClient(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}. 186 * 187 * @param builder Configuration options builder for HTTP4 provider 188 * @param rootName The root path 189 * @param fileSystemOptions The FileSystem options 190 * @return an {@link HttpClientBuilder} object 191 * @throws FileSystemException if an error occurs 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 * Create an {@link HttpClientContext} object for an http4 file system. 219 * 220 * @param builder Configuration options builder for http4 provider 221 * @param rootName The root path 222 * @param fileSystemOptions The FileSystem options 223 * @param authData The {@code UserAuthenticationData} object 224 * @return an {@link HttpClientContext} object 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 // set root port 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 // set proxy host port 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 * Create {@link SSLContext} for HttpClient. Invoked by {@link #createHttpClientBuilder(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}. 290 * 291 * @param builder Configuration options builder for HTTP4 provider 292 * @param fileSystemOptions The FileSystem options 293 * @return a {@link SSLContext} for HttpClient 294 * @throws FileSystemException if an error occurs 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}