/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.sync.engine.session; import com.btr.proxy.search.ProxySearch; import com.liferay.sync.engine.document.library.handler.Handler; import com.liferay.sync.engine.util.OSDetector; import com.liferay.sync.engine.util.PropsValues; import com.liferay.sync.engine.util.ReleaseInfo; import java.io.IOException; import java.io.OutputStream; import java.net.ProxySelector; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import oauth.signpost.OAuthConsumer; import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; import org.apache.commons.io.output.CountingOutputStream; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.HttpConnectionFactory; import org.apache.http.conn.ManagedHttpClientConnection; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.SystemDefaultRoutePlanner; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Shinn Lok * @author Dennis Ju */ public class Session { public static HttpClientBuilder createHttpClientBuilder( boolean trustSelfSigned, int maxConnections) { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); httpClientBuilder.disableAutomaticRetries(); RequestConfig.Builder builder = RequestConfig.custom(); builder.setConnectTimeout(PropsValues.SYNC_HTTP_CONNECTION_TIMEOUT); if (maxConnections == Integer.MAX_VALUE) { builder.setSocketTimeout(PropsValues.SYNC_HTTP_SOCKET_TIMEOUT * 2); } else { builder.setSocketTimeout(PropsValues.SYNC_HTTP_SOCKET_TIMEOUT); } List<Header> headers = new ArrayList<>(2); Header syncBuildHeader = new BasicHeader( "Sync-Build", String.valueOf(ReleaseInfo.getBuildNumber())); headers.add(syncBuildHeader); String syncDeviceType = null; if (OSDetector.isApple()) { syncDeviceType = "desktop-mac"; } else if (OSDetector.isLinux()) { syncDeviceType = "desktop-linux"; } else if (OSDetector.isWindows()) { syncDeviceType = "desktop-windows"; } Header syncDeviceTypeHeader = new BasicHeader( "Sync-Device", syncDeviceType); headers.add(syncDeviceTypeHeader); httpClientBuilder.setDefaultHeaders(headers); httpClientBuilder.setDefaultRequestConfig(builder.build()); httpClientBuilder.setMaxConnPerRoute(maxConnections); httpClientBuilder.setMaxConnTotal(maxConnections); httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy()); httpClientBuilder.setRoutePlanner(getHttpRoutePlanner()); try { if (trustSelfSigned) { httpClientBuilder.setSSLSocketFactory( _getTrustingSSLSocketFactory()); } else { httpClientBuilder.setSSLSocketFactory( _getDefaultSSLSocketFactory()); } } catch (Exception e) { _logger.error(e.getMessage(), e); } return httpClientBuilder; } public static HttpRoutePlanner getHttpRoutePlanner() { if (_httpRoutePlanner != null) { return _httpRoutePlanner; } ProxySearch proxySearch = ProxySearch.getDefaultProxySearch(); ProxySelector proxySelector = proxySearch.getProxySelector(); if (proxySelector == null) { proxySelector = ProxySelector.getDefault(); } _httpRoutePlanner = new SystemDefaultRoutePlanner(proxySelector); return _httpRoutePlanner; } public static void setTrustManagers(TrustManager[] trustManagers) throws Exception { SSLContextBuilder sslContextBuilder = SSLContexts.custom(); SSLContext sslContext = sslContextBuilder.build(); sslContext.init(null, trustManagers, new SecureRandom()); _defaultSSLSocketFactory = new SSLConnectionSocketFactory( sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } public Session( URL url, String login, String password, boolean trustSelfSigned, int maxConnections) { if (maxConnections == Integer.MAX_VALUE) { _executorService = Executors.newCachedThreadPool(); } else { _executorService = Executors.newFixedThreadPool(maxConnections); } HttpClientBuilder httpClientBuilder = createHttpClientBuilder( trustSelfSigned, maxConnections); httpClientBuilder.setConnectionManager( _getHttpClientConnectionManager(trustSelfSigned)); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); _httpHost = new HttpHost( url.getHost(), url.getPort(), url.getProtocol()); credentialsProvider.setCredentials( new AuthScope(_httpHost), new UsernamePasswordCredentials(login, password)); httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); _httpClient = httpClientBuilder.build(); _oAuthEnabled = false; } public Session( URL url, String oAuthConsumerKey, String oAuthConsumerSecret, String oAuthToken, String oAuthTokenSecret, boolean trustSelfSigned, int maxConnections) { if (maxConnections == Integer.MAX_VALUE) { _executorService = Executors.newCachedThreadPool(); } else { _executorService = Executors.newFixedThreadPool(maxConnections); } HttpClientBuilder httpClientBuilder = createHttpClientBuilder( trustSelfSigned, maxConnections); httpClientBuilder.setConnectionManager( _getHttpClientConnectionManager(trustSelfSigned)); _httpClient = httpClientBuilder.build(); _httpHost = new HttpHost( url.getHost(), url.getPort(), url.getProtocol()); _oAuthConsumer = new CommonsHttpOAuthConsumer( oAuthConsumerKey, oAuthConsumerSecret); _oAuthConsumer.setTokenWithSecret(oAuthToken, oAuthTokenSecret); _oAuthEnabled = true; } public void addHeader(String key, String value) { _headers.put(key, value); } public void asynchronousExecute( final HttpGet httpGet, final Handler<Void> handler) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { try { execute(httpGet, handler); } catch (Exception e) { handler.handleException(e); } } }; _executorService.execute(runnable); } public void asynchronousExecute( final HttpPost httpPost, final Map<String, Object> parameters, final Handler<Void> handler) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { try { execute(httpPost, parameters, handler); } catch (Exception e) { handler.handleException(e); } } }; _executorService.execute(runnable); } public HttpResponse execute( HttpPost httpPost, Map<String, Object> parameters) throws Exception { _buildHttpPostBody(httpPost, parameters); _prepareHttpRequest(httpPost); return _httpClient.execute(_httpHost, httpPost, _getBasicHttpContext()); } public <T> T execute( HttpPost httpPost, Map<String, Object> parameters, Handler<? extends T> handler) throws Exception { _buildHttpPostBody(httpPost, parameters); _prepareHttpRequest(httpPost); return _httpClient.execute( _httpHost, httpPost, handler, _getBasicHttpContext()); } public HttpResponse execute(HttpRequest httpRequest) throws Exception { return execute(httpRequest, _getBasicHttpContext()); } public <T> T execute(HttpRequest httpRequest, Handler<? extends T> handler) throws Exception { return execute(httpRequest, handler, _getBasicHttpContext()); } public <T> T execute( HttpRequest httpRequest, Handler<? extends T> handler, HttpContext httpContext) throws Exception { _prepareHttpRequest(httpRequest); return _httpClient.execute( _httpHost, httpRequest, handler, httpContext); } public HttpResponse execute( HttpRequest httpRequest, HttpContext httpContext) throws Exception { _prepareHttpRequest(httpRequest); return _httpClient.execute(_httpHost, httpRequest, httpContext); } public int getDownloadRate() { return _downloadRate; } public ExecutorService getExecutorService() { return _executorService; } public HttpClient getHttpClient() { return _httpClient; } public int getUploadRate() { return _uploadRate; } public void incrementDownloadedBytes(int bytes) { _downloadedBytes.getAndAdd(bytes); } public void incrementUploadedBytes(int bytes) { _uploadedBytes.getAndAdd(bytes); } public void startTrackTransferRate() { if ((_trackTransferRateScheduledFuture != null) && !_trackTransferRateScheduledFuture.isDone()) { return; } Runnable runnable = new Runnable() { @Override public void run() { _downloadRate = _downloadedBytes.get(); _downloadedBytes.set(0); _uploadRate = _uploadedBytes.get(); _uploadedBytes.set(0); } }; _trackTransferRateScheduledFuture = _scheduledExecutorService.scheduleAtFixedRate( runnable, 0, 1, TimeUnit.SECONDS); } public void stopTrackTransferRate() { if (_trackTransferRateScheduledFuture == null) { return; } _trackTransferRateScheduledFuture.cancel(false); } private static SSLConnectionSocketFactory _getDefaultSSLSocketFactory() throws Exception { if (_defaultSSLSocketFactory == null) { _defaultSSLSocketFactory = new SSLConnectionSocketFactory( SSLContext.getDefault()); } return _defaultSSLSocketFactory; } private static SSLConnectionSocketFactory _getTrustingSSLSocketFactory() throws Exception { if (_trustingSSLSocketFactory == null) { SSLContextBuilder sslContextBuilder = SSLContexts.custom(); sslContextBuilder.loadTrustMaterial( new TrustStrategy() { @Override public boolean isTrusted( X509Certificate[] x509Certificates, String authType) { return true; } }); _trustingSSLSocketFactory = new SSLConnectionSocketFactory( sslContextBuilder.build(), new NoopHostnameVerifier()); } return _trustingSSLSocketFactory; } private void _buildHttpPostBody( HttpPost httpPost, Map<String, Object> parameters) throws Exception { if (parameters.isEmpty()) { return; } HttpEntity httpEntity = _getEntity(parameters); httpPost.setEntity(httpEntity); } private BasicAuthCache _getBasicAuthCache() { BasicAuthCache basicAuthCache = new BasicAuthCache(); BasicScheme basicScheme = new BasicScheme(); basicAuthCache.put(_httpHost, basicScheme); return basicAuthCache; } private BasicHttpContext _getBasicHttpContext() { if (_basicHttpContext != null) { return _basicHttpContext; } _basicHttpContext = new BasicHttpContext(); _basicHttpContext.setAttribute( HttpClientContext.AUTH_CACHE, _getBasicAuthCache()); return _basicHttpContext; } private HttpEntity _getEntity(Map<String, Object> parameters) throws Exception { Path deltaFilePath = (Path)parameters.get("deltaFilePath"); Path filePath = (Path)parameters.get("filePath"); String zipFileIds = (String)parameters.get("zipFileIds"); Path zipFilePath = (Path)parameters.get("zipFilePath"); MultipartEntityBuilder multipartEntityBuilder = _getMultipartEntityBuilder(parameters); if (deltaFilePath != null) { multipartEntityBuilder.addPart( "deltaFile", _getFileBody( deltaFilePath, (String)parameters.get("mimeType"), (String)parameters.get("title"))); } else if (filePath != null) { multipartEntityBuilder.addPart( "file", _getFileBody( filePath, (String)parameters.get("mimeType"), (String)parameters.get("title"))); } else if (zipFileIds != null) { return _getURLEncodedFormEntity(parameters); } else if (zipFilePath != null) { multipartEntityBuilder.addPart( "zipFile", _getFileBody( zipFilePath, "application/zip", String.valueOf(zipFilePath.getFileName()))); } return multipartEntityBuilder.build(); } private ContentBody _getFileBody( Path filePath, String mimeType, String fileName) throws Exception { return new FileBody( filePath.toFile(), ContentType.create(mimeType), fileName) { @Override public void writeTo(OutputStream out) throws IOException { CountingOutputStream output = new CountingOutputStream(out) { @Override protected void beforeWrite(int n) { incrementUploadedBytes(n); super.beforeWrite(n); } }; super.writeTo(output); } }; } private HttpClientConnectionManager _getHttpClientConnectionManager( boolean trustSelfSigned) { HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory = new SyncManagedHttpClientConnectionFactory(); try { RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.create(); registryBuilder.register( "http", new PlainConnectionSocketFactory()); if (trustSelfSigned) { registryBuilder.register( "https", _getTrustingSSLSocketFactory()); } else { registryBuilder.register( "https", _getDefaultSSLSocketFactory()); } Registry<ConnectionSocketFactory> registry = registryBuilder.build(); return new PoolingHttpClientConnectionManager( registry, connectionFactory); } catch (Exception e) { _logger.error(e.getMessage(), e); } return new PoolingHttpClientConnectionManager(connectionFactory); } private MultipartEntityBuilder _getMultipartEntityBuilder( Map<String, Object> parameters) { MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); for (Map.Entry<String, Object> entry : parameters.entrySet()) { if (_ignoredParameterKeys.contains(entry.getKey())) { continue; } multipartEntityBuilder.addPart( entry.getKey(), _getStringBody(entry.getValue())); } return multipartEntityBuilder; } private StringBody _getStringBody(Object value) { return new StringBody( String.valueOf(value), ContentType.create( ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("UTF-8"))); } private HttpEntity _getURLEncodedFormEntity(Map<String, Object> parameters) throws Exception { List<NameValuePair> nameValuePairs = new ArrayList<>(); for (Map.Entry<String, Object> entry : parameters.entrySet()) { if (_ignoredParameterKeys.contains(entry.getKey())) { continue; } NameValuePair nameValuePair = new BasicNameValuePair( entry.getKey(), String.valueOf(entry.getValue())); nameValuePairs.add(nameValuePair); } return new UrlEncodedFormEntity(nameValuePairs); } private void _prepareHttpRequest(HttpRequest httpRequest) throws Exception { if (_oAuthEnabled) { _oAuthConsumer.sign(httpRequest); } for (Map.Entry<String, String> entry : _headers.entrySet()) { String key = entry.getKey(); if (_oAuthEnabled && key.equals("Sync-JWT")) { continue; } httpRequest.setHeader(key, entry.getValue()); } } private static final Logger _logger = LoggerFactory.getLogger( Session.class); private static SSLConnectionSocketFactory _defaultSSLSocketFactory; private static HttpRoutePlanner _httpRoutePlanner; private static final ScheduledExecutorService _scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private static SSLConnectionSocketFactory _trustingSSLSocketFactory; private BasicHttpContext _basicHttpContext; private final AtomicInteger _downloadedBytes = new AtomicInteger(0); private volatile int _downloadRate; private final ExecutorService _executorService; private final Map<String, String> _headers = new HashMap<>(); private final HttpClient _httpClient; private final HttpHost _httpHost; private final Set<String> _ignoredParameterKeys = new HashSet<>( Arrays.asList( "filePath", "handlers", "syncFile", "syncSite", "uiEvent")); private OAuthConsumer _oAuthConsumer; private final boolean _oAuthEnabled; private ScheduledFuture<?> _trackTransferRateScheduledFuture; private final AtomicInteger _uploadedBytes = new AtomicInteger(0); private volatile int _uploadRate; }