| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.vfs2.provider.http5; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.ProxySelector; |
| import java.security.KeyManagementException; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.CertificateException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.net.ssl.HostnameVerifier; |
| import javax.net.ssl.SSLContext; |
| |
| import org.apache.commons.vfs2.Capability; |
| import org.apache.commons.vfs2.FileName; |
| import org.apache.commons.vfs2.FileSystem; |
| import org.apache.commons.vfs2.FileSystemConfigBuilder; |
| import org.apache.commons.vfs2.FileSystemException; |
| import org.apache.commons.vfs2.FileSystemOptions; |
| import org.apache.commons.vfs2.UserAuthenticationData; |
| import org.apache.commons.vfs2.UserAuthenticator; |
| import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider; |
| import org.apache.commons.vfs2.provider.GenericFileName; |
| import org.apache.commons.vfs2.util.UserAuthenticatorUtils; |
| import org.apache.hc.client5.http.auth.AuthCache; |
| import org.apache.hc.client5.http.auth.AuthScope; |
| import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; |
| import org.apache.hc.client5.http.classic.HttpClient; |
| import org.apache.hc.client5.http.config.RequestConfig; |
| import org.apache.hc.client5.http.cookie.BasicCookieStore; |
| import org.apache.hc.client5.http.cookie.Cookie; |
| import org.apache.hc.client5.http.cookie.CookieStore; |
| import org.apache.hc.client5.http.impl.auth.BasicAuthCache; |
| import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; |
| import org.apache.hc.client5.http.impl.auth.BasicScheme; |
| import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; |
| import org.apache.hc.client5.http.impl.classic.HttpClients; |
| import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; |
| import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; |
| import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; |
| import org.apache.hc.client5.http.io.HttpClientConnectionManager; |
| import org.apache.hc.client5.http.protocol.HttpClientContext; |
| import org.apache.hc.client5.http.routing.HttpRoutePlanner; |
| import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; |
| import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; |
| import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; |
| import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; |
| import org.apache.hc.client5.http.ssl.TrustAllStrategy; |
| import org.apache.hc.core5.http.ConnectionReuseStrategy; |
| import org.apache.hc.core5.http.Header; |
| import org.apache.hc.core5.http.HttpHeaders; |
| import org.apache.hc.core5.http.HttpHost; |
| import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; |
| import org.apache.hc.core5.http.io.SocketConfig; |
| import org.apache.hc.core5.http.message.BasicHeader; |
| import org.apache.hc.core5.http.ssl.TLS; |
| import org.apache.hc.core5.ssl.SSLContextBuilder; |
| import org.apache.hc.core5.util.Timeout; |
| |
| /** |
| * {@code FileProvider} implementation using HttpComponents HttpClient v5 library. |
| * |
| * @since 2.5.0 |
| */ |
| public class Http5FileProvider extends AbstractOriginatingFileProvider { |
| |
| /** Authenticator information. */ |
| static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES = |
| new UserAuthenticationData.Type[] { |
| UserAuthenticationData.USERNAME, |
| UserAuthenticationData.PASSWORD |
| }; |
| |
| /** FileProvider capabilities */ |
| static final Collection<Capability> CAPABILITIES = |
| Collections.unmodifiableCollection( |
| Arrays.asList( |
| Capability.GET_TYPE, |
| Capability.READ_CONTENT, |
| Capability.URI, |
| Capability.GET_LAST_MODIFIED, |
| Capability.ATTRIBUTES, |
| Capability.RANDOM_ACCESS_READ, |
| Capability.DIRECTORY_READ_CONTENT |
| ) |
| ); |
| |
| /** |
| * Constructs a new provider. |
| */ |
| public Http5FileProvider() { |
| setFileNameParser(Http5FileNameParser.getInstance()); |
| } |
| |
| private HttpClientConnectionManager createConnectionManager(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| |
| final SocketConfig socketConfig = |
| SocketConfig |
| .custom() |
| .setSoTimeout(Timeout.ofMilliseconds(builder.getSoTimeoutDuration(fileSystemOptions).toMillis())) |
| .build(); |
| |
| final String[] tlsVersions = builder.getTlsVersions(fileSystemOptions).split("\\s*,\\s*"); |
| |
| final TLS[] tlsArray = Arrays.stream(tlsVersions).map(TLS::valueOf).toArray(TLS[]::new); |
| |
| final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() |
| .setSslContext(createSSLContext(builder, fileSystemOptions)) |
| .setHostnameVerifier(createHostnameVerifier(builder, fileSystemOptions)) |
| .setTlsVersions(tlsArray) |
| .build(); |
| |
| return PoolingHttpClientConnectionManagerBuilder.create() |
| .setSSLSocketFactory(sslSocketFactory) |
| .setMaxConnTotal(builder.getMaxTotalConnections(fileSystemOptions)) |
| .setMaxConnPerRoute(builder.getMaxConnectionsPerHost(fileSystemOptions)) |
| .setDefaultSocketConfig(socketConfig) |
| .build(); |
| } |
| |
| private CookieStore createDefaultCookieStore(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) { |
| final CookieStore cookieStore = new BasicCookieStore(); |
| final Cookie[] cookies = builder.getCookies(fileSystemOptions); |
| |
| if (cookies != null) { |
| Arrays.stream(cookies).forEach(cookieStore::addCookie); |
| } |
| |
| return cookieStore; |
| } |
| |
| private RequestConfig createDefaultRequestConfig(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) { |
| return RequestConfig.custom() |
| .setConnectTimeout(Timeout.ofMilliseconds(builder.getSoTimeoutDuration(fileSystemOptions).toMillis())) |
| .build(); |
| } |
| |
| private HostnameVerifier createHostnameVerifier(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| if (!builder.isHostnameVerificationEnabled(fileSystemOptions)) { |
| return NoopHostnameVerifier.INSTANCE; |
| } |
| |
| return new DefaultHostnameVerifier(); |
| } |
| |
| /** |
| * Create an {@link HttpClient} object for an http4 file system. |
| * |
| * @param builder Configuration options builder for http4 provider |
| * @param rootName The root path |
| * @param fileSystemOptions The file system options |
| * @return an {@link HttpClient} object |
| * @throws FileSystemException if an error occurs. |
| */ |
| protected HttpClient createHttpClient(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName, |
| final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| return createHttpClientBuilder(builder, rootName, fileSystemOptions).build(); |
| } |
| |
| /** |
| * Create an {@link HttpClientBuilder} object. Invoked by {@link #createHttpClient(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}. |
| * |
| * @param builder Configuration options builder for HTTP4 provider |
| * @param rootName The root path |
| * @param fileSystemOptions The FileSystem options |
| * @return an {@link HttpClientBuilder} object |
| * @throws FileSystemException if an error occurs |
| */ |
| protected HttpClientBuilder createHttpClientBuilder(final Http5FileSystemConfigBuilder builder, final GenericFileName rootName, |
| final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| final List<Header> defaultHeaders = new ArrayList<>(); |
| defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, builder.getUserAgent(fileSystemOptions))); |
| |
| final ConnectionReuseStrategy connectionReuseStrategy = builder.isKeepAlive(fileSystemOptions) |
| ? DefaultConnectionReuseStrategy.INSTANCE |
| : (request, response, context) -> false; |
| |
| final HttpClientBuilder httpClientBuilder = |
| HttpClients.custom() |
| .setRoutePlanner(createHttpRoutePlanner(builder, fileSystemOptions)) |
| .setConnectionManager(createConnectionManager(builder, fileSystemOptions)) |
| .setConnectionReuseStrategy(connectionReuseStrategy) |
| .setDefaultRequestConfig(createDefaultRequestConfig(builder, fileSystemOptions)) |
| .setDefaultHeaders(defaultHeaders) |
| .setDefaultCookieStore(createDefaultCookieStore(builder, fileSystemOptions)); |
| |
| if (!builder.getFollowRedirect(fileSystemOptions)) { |
| httpClientBuilder.disableRedirectHandling(); |
| } |
| |
| return httpClientBuilder; |
| } |
| |
| /** |
| * Create an {@link HttpClientContext} object for an http4 file system. |
| * |
| * @param builder Configuration options builder for http4 provider |
| * @param rootName The root path |
| * @param fileSystemOptions The FileSystem options |
| * @param authData The {@code UserAuthentiationData} object |
| * @return an {@link HttpClientContext} object |
| * @throws FileSystemException if an error occurs |
| */ |
| protected HttpClientContext createHttpClientContext(final Http5FileSystemConfigBuilder builder, |
| final GenericFileName rootName, final FileSystemOptions fileSystemOptions, |
| final UserAuthenticationData authData) throws FileSystemException { |
| |
| final HttpClientContext clientContext = HttpClientContext.create(); |
| final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); |
| clientContext.setCredentialsProvider(credsProvider); |
| |
| final String username = UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData, |
| UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName()))); |
| final char[] password = UserAuthenticatorUtils.getData(authData, |
| UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword())); |
| |
| if (username != null && !username.isEmpty()) { |
| // set root port |
| credsProvider.setCredentials(new AuthScope(rootName.getHostName(), rootName.getPort()), |
| new UsernamePasswordCredentials(username, password)); |
| } |
| |
| final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions); |
| |
| if (proxyHost != null) { |
| final UserAuthenticator proxyAuth = builder.getProxyAuthenticator(fileSystemOptions); |
| |
| if (proxyAuth != null) { |
| final UserAuthenticationData proxyAuthData = UserAuthenticatorUtils.authenticate(proxyAuth, |
| new UserAuthenticationData.Type[] { UserAuthenticationData.USERNAME, |
| UserAuthenticationData.PASSWORD }); |
| |
| if (proxyAuthData != null) { |
| final UsernamePasswordCredentials proxyCreds = new UsernamePasswordCredentials( |
| UserAuthenticatorUtils.toString( |
| UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME, null)), |
| UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD, null)); |
| |
| // set proxy host port |
| credsProvider.setCredentials(new AuthScope(proxyHost.getHostName(), proxyHost.getPort()), |
| proxyCreds); |
| } |
| |
| if (builder.isPreemptiveAuth(fileSystemOptions)) { |
| final AuthCache authCache = new BasicAuthCache(); |
| final BasicScheme basicAuth = new BasicScheme(); |
| authCache.put(proxyHost, basicAuth); |
| clientContext.setAuthCache(authCache); |
| } |
| } |
| } |
| |
| return clientContext; |
| } |
| |
| private HttpRoutePlanner createHttpRoutePlanner(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) { |
| final HttpHost proxyHost = getProxyHttpHost(builder, fileSystemOptions); |
| |
| if (proxyHost != null) { |
| return new DefaultProxyRoutePlanner(proxyHost); |
| } |
| |
| return new SystemDefaultRoutePlanner(ProxySelector.getDefault()); |
| } |
| |
| /** |
| * Create {@link SSLContext} for HttpClient. Invoked by {@link #createHttpClientBuilder(Http5FileSystemConfigBuilder, GenericFileName, FileSystemOptions)}. |
| * |
| * @param builder Configuration options builder for HTTP4 provider |
| * @param fileSystemOptions The FileSystem options |
| * @return a {@link SSLContext} for HttpClient |
| * @throws FileSystemException if an error occurs |
| */ |
| protected SSLContext createSSLContext(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) throws FileSystemException { |
| try { |
| final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); |
| sslContextBuilder.setKeyStoreType(builder.getKeyStoreType(fileSystemOptions)); |
| |
| File keystoreFileObject = null; |
| final String keystoreFile = builder.getKeyStoreFile(fileSystemOptions); |
| |
| if (keystoreFile != null && !keystoreFile.isEmpty()) { |
| keystoreFileObject = new File(keystoreFile); |
| } |
| |
| if (keystoreFileObject != null && keystoreFileObject.exists()) { |
| final String keystorePass = builder.getKeyStorePass(fileSystemOptions); |
| final char[] keystorePassChars = (keystorePass != null) ? keystorePass.toCharArray() : null; |
| sslContextBuilder.loadTrustMaterial(keystoreFileObject, keystorePassChars, TrustAllStrategy.INSTANCE); |
| } else { |
| sslContextBuilder.loadTrustMaterial(TrustAllStrategy.INSTANCE); |
| } |
| |
| return sslContextBuilder.build(); |
| } catch (final KeyStoreException e) { |
| throw new FileSystemException("Keystore error. " + e.getMessage(), e); |
| } catch (final KeyManagementException e) { |
| throw new FileSystemException("Cannot retrieve keys. " + e.getMessage(), e); |
| } catch (final NoSuchAlgorithmException e) { |
| throw new FileSystemException("Algorithm error. " + e.getMessage(), e); |
| } catch (final CertificateException e) { |
| throw new FileSystemException("Certificate error. " + e.getMessage(), e); |
| } catch (final IOException e) { |
| throw new FileSystemException("Cannot open key file. " + e.getMessage(), e); |
| } |
| } |
| |
| @Override |
| protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions) |
| throws FileSystemException { |
| final GenericFileName rootName = (GenericFileName) name; |
| |
| UserAuthenticationData authData = null; |
| HttpClient httpClient; |
| HttpClientContext httpClientContext; |
| |
| try { |
| final Http5FileSystemConfigBuilder builder = Http5FileSystemConfigBuilder.getInstance(); |
| authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES); |
| httpClientContext = createHttpClientContext(builder, rootName, fileSystemOptions, authData); |
| httpClient = createHttpClient(builder, rootName, fileSystemOptions); |
| } finally { |
| UserAuthenticatorUtils.cleanup(authData); |
| } |
| |
| return new Http5FileSystem(rootName, fileSystemOptions, httpClient, httpClientContext); |
| } |
| |
| @Override |
| public Collection<Capability> getCapabilities() { |
| return CAPABILITIES; |
| } |
| |
| @Override |
| public FileSystemConfigBuilder getConfigBuilder() { |
| return Http5FileSystemConfigBuilder.getInstance(); |
| } |
| |
| private HttpHost getProxyHttpHost(final Http5FileSystemConfigBuilder builder, |
| final FileSystemOptions fileSystemOptions) { |
| final String proxyScheme = builder.getProxyScheme(fileSystemOptions); |
| final String proxyHost = builder.getProxyHost(fileSystemOptions); |
| final int proxyPort = builder.getProxyPort(fileSystemOptions); |
| |
| if (proxyHost != null && !proxyHost.isEmpty() && proxyPort > 0) { |
| return new HttpHost(proxyScheme, proxyHost, proxyPort); |
| } |
| |
| return null; |
| } |
| |
| } |