| /* |
| * Copyright (c) 2014-2017 Eike Stepper (Loehne, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.oomph.setup.internal.core.util; |
| |
| import org.eclipse.oomph.base.util.BaseUtil; |
| import org.eclipse.oomph.internal.setup.SetupProperties; |
| import org.eclipse.oomph.preferences.util.PreferencesUtil; |
| import org.eclipse.oomph.setup.internal.core.SetupContext; |
| import org.eclipse.oomph.setup.internal.core.SetupCorePlugin; |
| import org.eclipse.oomph.setup.internal.core.util.ECFURIHandlerImpl.AuthorizationHandler.Authorization; |
| import org.eclipse.oomph.setup.util.SetupUtil; |
| import org.eclipse.oomph.util.IOExceptionWithCause; |
| import org.eclipse.oomph.util.IORuntimeException; |
| import org.eclipse.oomph.util.IOUtil; |
| import org.eclipse.oomph.util.OS; |
| import org.eclipse.oomph.util.ObjectUtil; |
| import org.eclipse.oomph.util.PropertiesUtil; |
| import org.eclipse.oomph.util.ReflectUtil; |
| import org.eclipse.oomph.util.StringUtil; |
| import org.eclipse.oomph.util.WorkerPool; |
| |
| import org.eclipse.emf.common.CommonPlugin; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.URIConverter; |
| import org.eclipse.emf.ecore.resource.impl.URIHandlerImpl; |
| import org.eclipse.emf.ecore.xml.type.XMLTypeFactory; |
| |
| import org.eclipse.core.net.proxy.IProxyData; |
| import org.eclipse.core.net.proxy.IProxyService; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.ecf.core.ContainerCreateException; |
| import org.eclipse.ecf.core.ContainerFactory; |
| import org.eclipse.ecf.core.IContainer; |
| import org.eclipse.ecf.core.security.ConnectContextFactory; |
| import org.eclipse.ecf.core.util.ECFException; |
| import org.eclipse.ecf.core.util.Proxy; |
| import org.eclipse.ecf.core.util.ProxyAddress; |
| import org.eclipse.ecf.filetransfer.BrowseFileTransferException; |
| import org.eclipse.ecf.filetransfer.IFileTransferListener; |
| import org.eclipse.ecf.filetransfer.IIncomingFileTransfer; |
| import org.eclipse.ecf.filetransfer.IRemoteFile; |
| import org.eclipse.ecf.filetransfer.IRemoteFileInfo; |
| import org.eclipse.ecf.filetransfer.IRemoteFileSystemBrowserContainerAdapter; |
| import org.eclipse.ecf.filetransfer.IRemoteFileSystemListener; |
| import org.eclipse.ecf.filetransfer.IRetrieveFileTransferContainerAdapter; |
| import org.eclipse.ecf.filetransfer.IRetrieveFileTransferOptions; |
| import org.eclipse.ecf.filetransfer.IncomingFileTransferException; |
| import org.eclipse.ecf.filetransfer.UserCancelledException; |
| import org.eclipse.ecf.filetransfer.events.IFileTransferConnectStartEvent; |
| import org.eclipse.ecf.filetransfer.events.IFileTransferEvent; |
| import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveDoneEvent; |
| import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveStartEvent; |
| import org.eclipse.ecf.filetransfer.events.IRemoteFileSystemBrowseEvent; |
| import org.eclipse.ecf.filetransfer.events.IRemoteFileSystemEvent; |
| import org.eclipse.ecf.filetransfer.identity.IFileID; |
| import org.eclipse.ecf.provider.filetransfer.identity.FileTransferID; |
| import org.eclipse.ecf.provider.filetransfer.identity.FileTransferNamespace; |
| import org.eclipse.ecf.provider.filetransfer.util.ProxySetupHelper; |
| import org.eclipse.equinox.p2.core.UIServices; |
| import org.eclipse.equinox.p2.core.UIServices.AuthenticationInfo; |
| import org.eclipse.equinox.security.storage.ISecurePreferences; |
| import org.eclipse.equinox.security.storage.StorageException; |
| |
| import org.apache.http.cookie.Cookie; |
| import org.apache.http.cookie.CookieSpecProvider; |
| import org.osgi.framework.Version; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.CookieManager; |
| import java.net.CookieStore; |
| import java.net.HttpCookie; |
| import java.net.HttpURLConnection; |
| import java.net.SocketTimeoutException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLEncoder; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public class ECFURIHandlerImpl extends URIHandlerImpl implements URIResolver |
| { |
| public static final String OPTION_CACHE_HANDLING = "OPTION_CACHE_HANDLING"; |
| |
| public static final String OPTION_AUTHORIZATION_HANDLER = "OPTION_AUTHORIZATION_HANDLER"; |
| |
| public static final String OPTION_AUTHORIZATION = "OPTION_AUTHORIZATION"; |
| |
| public static final String OPTION_PROXY_AUTHORIZATION = "OPTION_PROXY_AUTHORIZATION"; |
| |
| public static final String OPTION_MONITOR = "OPTION_MONITOR"; |
| |
| public static final CookieStore COOKIE_STORE = FileTransferListener.DELEGATING_COOKIE_STORE; |
| |
| public static final String OPTION_LOGIN_URI = "OPTION_LOGIN_URI"; |
| |
| public static final String OPTION_FORM_URI = "OPTION_FORM_URI"; |
| |
| public static final String OPTION_BASIC_AUTHENTICATION = "OPTION_BASIC_AUTHENTICATION"; |
| |
| private static final String FAILED_EXPECTED_ETAG = "-1"; |
| |
| private static final URI CACHE_FOLDER = SetupContext.GLOBAL_STATE_LOCATION_URI.appendSegment("cache"); |
| |
| private static final Map<URI, String> EXPECTED_ETAGS = new HashMap<URI, String>(); |
| |
| private static final Map<URI, IOException> EXPECTED_EXCEPTIONS = Collections.synchronizedMap(new HashMap<URI, IOException>()); |
| |
| private static final Map<URI, CountDownLatch> LOCKS = new HashMap<URI, CountDownLatch>(); |
| |
| private static final boolean TEST_IO_EXCEPTION = false; |
| |
| private static final boolean TEST_SLOW_NETWORK = false; |
| |
| private static final boolean TRACE = PropertiesUtil.isProperty(SetupProperties.PROP_SETUP_ECF_TRACE); |
| |
| private static final String API_GITHUB_HOST = "api.github.com"; |
| |
| private static final String CONTENT_TAG = "\"content\":\""; |
| |
| private static final int CONNECT_TIMEOUT = PropertiesUtil.getProperty(SetupProperties.PROP_SETUP_ECF_CONNECT_TIMEOUT, 10000); |
| |
| private static final int READ_TIMEOUT = PropertiesUtil.getProperty(SetupProperties.PROP_SETUP_ECF_READ_TIMEOUT, 10000); |
| |
| private static final URI ACTUAL_INDEX_SETUP_ARCHIVE_LOCATION_URI = URI |
| .createURI(SetupContext.INDEX_SETUP_ARCHIVE_LOCATION_URI.toString().replace("http:", "https:")); |
| |
| private static boolean loggedBlockedURI; |
| |
| private static final String USER_AGENT; |
| static |
| { |
| String userAgentProperty = PropertiesUtil.getProperty(SetupProperties.PROP_SETUP_USER_AGENT); |
| if (userAgentProperty == null) |
| { |
| StringBuilder userAgent = new StringBuilder("eclipse/oomph/"); |
| if (SetupUtil.INSTALLER_APPLICATION) |
| { |
| userAgent.append("installer/"); |
| } |
| else if (SetupUtil.SETUP_ARCHIVER_APPLICATION) |
| { |
| userAgent.append("archiver/"); |
| } |
| |
| Version oomphVersion = SetupCorePlugin.INSTANCE.getBundle().getVersion(); |
| userAgent.append(oomphVersion); |
| USER_AGENT = userAgent.toString(); |
| } |
| else |
| { |
| USER_AGENT = userAgentProperty; |
| } |
| } |
| |
| private AuthorizationHandler defaultAuthorizationHandler; |
| |
| public ECFURIHandlerImpl(AuthorizationHandler defaultAuthorizationHandler) |
| { |
| this.defaultAuthorizationHandler = defaultAuthorizationHandler; |
| } |
| |
| public URI resolve(URI uri) |
| { |
| return transform(uri, null); |
| } |
| |
| @Override |
| public Map<String, ?> getAttributes(URI uri, Map<?, ?> options) |
| { |
| if (uri.scheme().startsWith("http")) |
| { |
| Set<String> requestedAttributes = getRequestedAttributes(options); |
| if (requestedAttributes != null && requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY) && requestedAttributes.size() == 1) |
| { |
| // For performance reasons, this assumes that all http/https accessible files are read only. |
| Map<String, Object> result = new HashMap<String, Object>(); |
| result.put(URIConverter.ATTRIBUTE_READ_ONLY, true); |
| return result; |
| } |
| } |
| |
| return getRemoteAttributes(uri, options); |
| } |
| |
| private Map<String, ?> getRemoteAttributes(URI uri, Map<?, ?> options) |
| { |
| if (uri.isPlatform()) |
| { |
| return super.getAttributes(uri, options); |
| } |
| |
| try |
| { |
| return new RemoteAttributionsConnectionHandler(uri, options).process(); |
| } |
| catch (IOException ex) |
| { |
| return Collections.emptyMap(); |
| } |
| } |
| |
| private final Map<String, ?> handleResponseAttributes(Set<String> requestedAttributes, Map<Object, Object> response) |
| { |
| Map<String, Object> result = new HashMap<String, Object>(); |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY)) |
| { |
| result.put(URIConverter.ATTRIBUTE_READ_ONLY, true); |
| } |
| |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_TIME_STAMP)) |
| { |
| Object timeStamp = response.get(URIConverter.RESPONSE_TIME_STAMP_PROPERTY); |
| if (timeStamp != null) |
| { |
| result.put(URIConverter.ATTRIBUTE_TIME_STAMP, timeStamp); |
| } |
| } |
| |
| return result; |
| } |
| |
| private final Map<String, ?> handleAttributes(Set<String> requestedAttributes, IRemoteFileInfo info) |
| { |
| Map<String, Object> result = new HashMap<String, Object>(); |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY)) |
| { |
| result.put(URIConverter.ATTRIBUTE_READ_ONLY, true); |
| } |
| |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_TIME_STAMP)) |
| { |
| result.put(URIConverter.ATTRIBUTE_TIME_STAMP, info.getLastModified()); |
| } |
| |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_LENGTH)) |
| { |
| result.put(URIConverter.ATTRIBUTE_LENGTH, info.getLength()); |
| } |
| |
| return result; |
| } |
| |
| private final Map<String, ?> handleAttributes(Set<String> requestedAttributes, Map<String, ?> attributes) |
| { |
| Map<String, Object> result = new HashMap<String, Object>(); |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_READ_ONLY)) |
| { |
| result.put(URIConverter.ATTRIBUTE_READ_ONLY, true); |
| } |
| |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_TIME_STAMP)) |
| { |
| Object timeStamp = attributes.get(URIConverter.ATTRIBUTE_TIME_STAMP); |
| if (timeStamp != null) |
| { |
| result.put(URIConverter.ATTRIBUTE_TIME_STAMP, timeStamp); |
| } |
| } |
| |
| if (requestedAttributes == null || requestedAttributes.contains(URIConverter.ATTRIBUTE_LENGTH)) |
| { |
| Object length = attributes.get(URIConverter.ATTRIBUTE_LENGTH); |
| if (length != null) |
| { |
| result.put(URIConverter.ATTRIBUTE_LENGTH, length); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public boolean exists(URI uri, Map<?, ?> options) |
| { |
| return !getAttributes(uri, options).isEmpty(); |
| } |
| |
| @Override |
| public InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException |
| { |
| if (uri.isPlatform()) |
| { |
| return super.createInputStream(uri, options); |
| } |
| |
| return new InputStreamConnectionHandler(uri, options).process(); |
| } |
| |
| private static CountDownLatch acquireLock(URI uri) throws IOException |
| { |
| CountDownLatch countDownLatch = null; |
| synchronized (LOCKS) |
| { |
| countDownLatch = LOCKS.get(uri); |
| if (countDownLatch == null) |
| { |
| countDownLatch = new CountDownLatch(1); |
| LOCKS.put(uri, countDownLatch); |
| return countDownLatch; |
| } |
| } |
| |
| try |
| { |
| countDownLatch.await(); |
| return acquireLock(uri); |
| } |
| catch (InterruptedException ex) |
| { |
| throw new IOExceptionWithCause(ex); |
| } |
| } |
| |
| private static void releaseLock(URI uri, CountDownLatch countDownLatch) |
| { |
| synchronized (LOCKS) |
| { |
| LOCKS.remove(uri); |
| } |
| |
| countDownLatch.countDown(); |
| } |
| |
| private IContainer createContainer() throws IOException |
| { |
| try |
| { |
| return ContainerFactory.getDefault().createContainer(); |
| } |
| catch (ContainerCreateException ex) |
| { |
| throw new IOExceptionWithCause(ex); |
| } |
| } |
| |
| public static Set<? extends URI> clearExpectedETags() |
| { |
| Set<URI> result; |
| synchronized (EXPECTED_ETAGS) |
| { |
| result = new HashSet<URI>(EXPECTED_ETAGS.keySet()); |
| EXPECTED_ETAGS.clear(); |
| } |
| |
| EXPECTED_EXCEPTIONS.clear(); |
| |
| return result; |
| } |
| |
| public static Job mirror(final Set<? extends URI> uris) |
| { |
| Job job = new Job("ETag Mirror") |
| { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) |
| { |
| new ETagMirror().begin(uris, monitor); |
| return Status.OK_STATUS; |
| } |
| }; |
| |
| job.schedule(); |
| return job; |
| } |
| |
| public static URI getCacheFile(URI uri) |
| { |
| return CACHE_FOLDER.appendSegment( |
| IOUtil.encodeFileName((SetupContext.INDEX_SETUP_ARCHIVE_LOCATION_URI.equals(uri) ? ACTUAL_INDEX_SETUP_ARCHIVE_LOCATION_URI : uri).toString())); |
| } |
| |
| public static String getETag(URIConverter uriConverter, URI file) |
| { |
| if (uriConverter.exists(file, null)) |
| { |
| URI eTagFile = file.appendFileExtension("etag"); |
| if (uriConverter.exists(eTagFile, null)) |
| { |
| try |
| { |
| return new String(BaseUtil.readFile(uriConverter, null, eTagFile), "UTF-8"); |
| } |
| catch (IORuntimeException ex) |
| { |
| // If we can't read the ETag, we'll just return null. |
| } |
| catch (UnsupportedEncodingException ex) |
| { |
| // All systems support UTF-8. |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private static void setETag(URIConverter uriConverter, URI file, String eTag) |
| { |
| try |
| { |
| if (eTag != null) |
| { |
| BaseUtil.writeFile(uriConverter, null, file.appendFileExtension("etag"), eTag.getBytes("UTF-8")); |
| } |
| else |
| { |
| BaseUtil.deleteFile(uriConverter, null, file); |
| } |
| } |
| catch (IORuntimeException ex) |
| { |
| // If we can't write the ETag, perhaps some other process is writing it, but it's expected to write the same ETag value. |
| } |
| catch (UnsupportedEncodingException ex) |
| { |
| // All systems support UTF-8. |
| } |
| } |
| |
| private AuthorizationHandler getAuthorizatonHandler(Map<?, ?> options) |
| { |
| if (options.containsKey(OPTION_AUTHORIZATION_HANDLER)) |
| { |
| return (AuthorizationHandler)options.get(OPTION_AUTHORIZATION_HANDLER); |
| } |
| |
| return defaultAuthorizationHandler; |
| } |
| |
| private static String getHost(java.net.URI uri) |
| { |
| return uri.getHost(); |
| } |
| |
| private static String getHost(URI uri) |
| { |
| String authority = uri.authority(); |
| if (authority != null) |
| { |
| int i = authority.indexOf('@'); |
| int j = authority.indexOf(':', i + 1); |
| return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j); |
| } |
| |
| return null; |
| } |
| |
| @SuppressWarnings("all") |
| private static Date parseHTTPDate(String string) |
| { |
| try |
| { |
| return org.apache.http.impl.cookie.DateUtils.parseDate(string); |
| } |
| catch (Exception ex) |
| { |
| //$FALL-THROUGH$ |
| } |
| |
| return null; |
| } |
| |
| static String getExpectedETag(URI uri) |
| { |
| synchronized (EXPECTED_ETAGS) |
| { |
| String result = EXPECTED_ETAGS.get(uri); |
| return result == null && SetupContext.INDEX_SETUP_ARCHIVE_LOCATION_URI.equals(uri) ? EXPECTED_ETAGS.get(ACTUAL_INDEX_SETUP_ARCHIVE_LOCATION_URI) : result; |
| } |
| } |
| |
| static void setExpectedETag(URI uri, String eTag) |
| { |
| synchronized (EXPECTED_ETAGS) |
| { |
| String originalExpectedETag = EXPECTED_ETAGS.put(uri, eTag); |
| if (eTag == null && originalExpectedETag != null) |
| { |
| EXPECTED_ETAGS.put(uri, originalExpectedETag); |
| } |
| } |
| } |
| |
| private static CacheHandling getCacheHandling(Map<?, ?> options) |
| { |
| CacheHandling cacheHandling = (CacheHandling)options.get(OPTION_CACHE_HANDLING); |
| if (cacheHandling == null) |
| { |
| cacheHandling = CacheHandling.CACHE_WITH_ETAG_CHECKING; |
| } |
| |
| return cacheHandling; |
| } |
| |
| private static Authorization getAuthorizaton(Map<?, ?> options) |
| { |
| return (Authorization)options.get(OPTION_AUTHORIZATION); |
| } |
| |
| private static Authorization getProxyAuthorizaton(Map<?, ?> options) |
| { |
| return (Authorization)options.get(OPTION_PROXY_AUTHORIZATION); |
| } |
| |
| private static IOException createIOException(String url, Throwable cause) |
| { |
| String message = cause.getMessage(); |
| if (message != null && message.contains(url)) |
| { |
| if (cause instanceof IOException) |
| { |
| return (IOException)cause; |
| } |
| |
| return new IOExceptionWithCause(cause); |
| } |
| |
| return new IOExceptionWithCause((StringUtil.isEmpty(message) ? "Error: " : message + ": ") + url, cause); |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| public enum CacheHandling |
| { |
| CACHE_ONLY, CACHE_WITHOUT_ETAG_CHECKING, CACHE_WITH_ETAG_CHECKING, CACHE_IGNORE |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| public interface AuthorizationHandler |
| { |
| public Authorization authorize(URI uri); |
| |
| public Authorization reauthorize(URI uri, Authorization authorization); |
| |
| /** |
| * @author Ed Merks |
| */ |
| public static final class Authorization |
| { |
| public static final Authorization UNAUTHORIZED = new Authorization("", ""); |
| |
| public static final Authorization UNAUTHORIZEABLE = new Authorization("", ""); |
| |
| private final String user; |
| |
| private final String password; |
| |
| private boolean saved; |
| |
| public Authorization(String user, String password) |
| { |
| this.user = user == null ? "" : user; |
| this.password = obscure(password == null ? "" : password); |
| } |
| |
| public String getUser() |
| { |
| return user; |
| } |
| |
| public String getPassword() |
| { |
| return unobscure(password); |
| } |
| |
| public String getAuthorization() |
| { |
| return "Basic " + obscure(user.length() == 0 ? getPassword() : user + ":" + getPassword()); |
| } |
| |
| public boolean isAuthorized() |
| { |
| return !"".equals(password); |
| } |
| |
| public boolean isUnauthorizeable() |
| { |
| return this == UNAUTHORIZEABLE; |
| } |
| |
| private String obscure(String string) |
| { |
| return XMLTypeFactory.eINSTANCE.convertBase64Binary(string.getBytes()); |
| } |
| |
| private String unobscure(String string) |
| { |
| return new String(XMLTypeFactory.eINSTANCE.createBase64Binary(string)); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + (user == null ? 0 : user.hashCode()); |
| result = prime * result + (password == null ? 0 : password.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) |
| { |
| if (this == obj) |
| { |
| return true; |
| } |
| |
| if (obj == null) |
| { |
| return false; |
| } |
| |
| if (getClass() != obj.getClass()) |
| { |
| return false; |
| } |
| |
| if (this == UNAUTHORIZEABLE) |
| { |
| return obj == UNAUTHORIZEABLE; |
| } |
| |
| Authorization other = (Authorization)obj; |
| if (!user.equals(other.user)) |
| { |
| return false; |
| } |
| |
| if (!password.equals(other.password)) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| public boolean isSaved() |
| { |
| return saved; |
| } |
| |
| public void setSaved(boolean saved) |
| { |
| this.saved = saved; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return this == UNAUTHORIZEABLE ? "Authorization [unauthorizeable]" |
| : "Authorization [user=" + user + ", password=" + password + "]" + (saved ? " saved" : ""); |
| } |
| |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| public static class AuthorizationHandlerImpl implements AuthorizationHandler |
| { |
| private final Map<String, Authorization> authorizations = new HashMap<String, Authorization>(); |
| |
| private final UIServices uiServices; |
| |
| private ISecurePreferences securePreferences; |
| |
| public AuthorizationHandlerImpl(UIServices uiServices, ISecurePreferences securePreferences) |
| { |
| this.uiServices = uiServices; |
| this.securePreferences = securePreferences; |
| } |
| |
| public synchronized void clearCache() |
| { |
| authorizations.clear(); |
| } |
| |
| public synchronized Authorization authorize(URI uri) |
| { |
| String host = getHost(uri); |
| if (host != null) |
| { |
| Authorization cachedAuthorization = authorizations.get(host); |
| if (cachedAuthorization == Authorization.UNAUTHORIZEABLE) |
| { |
| return cachedAuthorization; |
| } |
| |
| if (securePreferences != null) |
| { |
| try |
| { |
| ISecurePreferences node = securePreferences.node(host); |
| String user = node.get("user", ""); |
| String password = node.get("password", ""); |
| |
| Authorization authorization = new Authorization(user, password); |
| if (authorization.isAuthorized()) |
| { |
| authorization.setSaved(true); |
| authorizations.put(host, authorization); |
| return authorization; |
| } |
| } |
| catch (StorageException ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex); |
| } |
| } |
| |
| if (cachedAuthorization != null) |
| { |
| return cachedAuthorization; |
| } |
| } |
| |
| return Authorization.UNAUTHORIZED; |
| } |
| |
| public synchronized Authorization reauthorize(URI uri, Authorization authorization) |
| { |
| // Double check that another thread hasn't already prompted and updated the secure store or has not already permanently failed to authorize. |
| Authorization currentAuthorization = authorize(uri); |
| if (!currentAuthorization.equals(authorization) && currentAuthorization.isAuthorized() || currentAuthorization == Authorization.UNAUTHORIZEABLE) |
| { |
| return currentAuthorization; |
| } |
| |
| if (uiServices != null) |
| { |
| String host = getHost(uri); |
| if (host != null) |
| { |
| AuthenticationInfo currentAuthenticationInfo = new AuthenticationInfo(authorization.getUser(), authorization.getPassword(), authorization.isSaved()); |
| AuthenticationInfo authenticationInfo = uiServices.getUsernamePassword(uri.toString(), currentAuthenticationInfo); |
| if (authenticationInfo != null) |
| { |
| String user = authenticationInfo.getUserName(); |
| String password = authenticationInfo.getPassword(); |
| Authorization reauthorization = new Authorization(user, password); |
| if (reauthorization.isAuthorized()) |
| { |
| if (authenticationInfo.saveResult() && securePreferences != null) |
| { |
| try |
| { |
| ISecurePreferences node = securePreferences.node(host); |
| node.put("user", user, false); |
| node.put("password", password, true); |
| node.flush(); |
| reauthorization.setSaved(true); |
| } |
| catch (IOException ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex); |
| } |
| catch (StorageException ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex); |
| } |
| } |
| |
| authorizations.put(host, reauthorization); |
| return reauthorization; |
| } |
| } |
| else |
| { |
| authorizations.put(host, Authorization.UNAUTHORIZEABLE); |
| return Authorization.UNAUTHORIZEABLE; |
| } |
| } |
| } |
| |
| return currentAuthorization; |
| } |
| |
| @Override |
| public String toString() |
| { |
| StringBuilder result = new StringBuilder(super.toString()); |
| result.append(" authorizations: "); |
| result.append(authorizations); |
| result.append(" securePreferences: "); |
| result.append(securePreferences); |
| result.append(" uiServices: "); |
| result.append(uiServices); |
| return result.toString(); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class FileTransferListener implements IFileTransferListener, ConnectionListener |
| { |
| @SuppressWarnings("all") |
| private static final org.apache.http.impl.client.BasicCookieStore COOKIE_STORE = new org.apache.http.impl.client.BasicCookieStore(); |
| |
| private static final DelegatingCookieStore DELEGATING_COOKIE_STORE = new DelegatingCookieStore(COOKIE_STORE); |
| |
| public final CountDownLatch receiveLatch = new CountDownLatch(1); |
| |
| public final String expectedETag; |
| |
| public String eTag; |
| |
| public ByteArrayOutputStream out; |
| |
| public long lastModified; |
| |
| public Exception exception; |
| |
| private IProgressMonitor monitor; |
| |
| public FileTransferListener(String expectedETag, IProgressMonitor monitor) |
| { |
| this.expectedETag = expectedETag; |
| this.monitor = monitor; |
| } |
| |
| public void handleTransferEvent(IFileTransferEvent event) |
| { |
| if (event instanceof IFileTransferConnectStartEvent) |
| { |
| IFileTransferConnectStartEvent connectStartEvent = (IFileTransferConnectStartEvent)event; |
| if (monitor != null && monitor.isCanceled()) |
| { |
| connectStartEvent.cancel(); |
| |
| // Older versions of ECF don't produce a IIncomingFileTransferReceiveDoneEvent. |
| exception = new UserCancelledException(); |
| receiveLatch.countDown(); |
| return; |
| } |
| |
| applyCookieStore(connectStartEvent); |
| } |
| else if (event instanceof IIncomingFileTransferReceiveStartEvent) |
| { |
| IIncomingFileTransferReceiveStartEvent receiveStartEvent = (IIncomingFileTransferReceiveStartEvent)event; |
| if (monitor != null && monitor.isCanceled()) |
| { |
| receiveStartEvent.cancel(); |
| |
| // Older versions of ECF don't produce a IIncomingFileTransferReceiveDoneEvent. |
| exception = new UserCancelledException(); |
| receiveLatch.countDown(); |
| return; |
| } |
| |
| out = new ByteArrayOutputStream(); |
| |
| @SuppressWarnings("rawtypes") |
| Map responseHeaders = receiveStartEvent.getResponseHeaders(); |
| if (responseHeaders != null) |
| { |
| eTag = (String)responseHeaders.get("ETag"); |
| |
| String lastModifiedValue = (String)responseHeaders.get("Last-Modified"); |
| if (lastModifiedValue != null) |
| { |
| Date date = parseHTTPDate(lastModifiedValue.toString()); |
| if (date != null) |
| { |
| lastModified = date.getTime(); |
| if (eTag == null) |
| { |
| eTag = Long.toString(lastModified); |
| } |
| } |
| } |
| |
| if (lastModified == 0) |
| { |
| lastModified = System.currentTimeMillis(); |
| if (eTag == null) |
| { |
| eTag = Long.toString(lastModified); |
| } |
| } |
| |
| if (expectedETag != null && expectedETag.equals(eTag)) |
| { |
| receiveStartEvent.cancel(); |
| |
| // Older versions of ECF don't produce a IIncomingFileTransferReceiveDoneEvent. |
| exception = new UserCancelledException(); |
| receiveLatch.countDown(); |
| return; |
| } |
| } |
| |
| try |
| { |
| receiveStartEvent.receive(out); |
| } |
| catch (IOException ex) |
| { |
| exception = ex; |
| } |
| } |
| else if (event instanceof IIncomingFileTransferReceiveDoneEvent) |
| { |
| IIncomingFileTransferReceiveDoneEvent done = (IIncomingFileTransferReceiveDoneEvent)event; |
| Exception ex = done.getException(); |
| if (ex != null && exception == null) |
| { |
| exception = ex; |
| } |
| |
| receiveLatch.countDown(); |
| } |
| } |
| |
| private static void applyCookieStore(final IFileTransferConnectStartEvent connectStartEvent) |
| { |
| IIncomingFileTransfer fileTransfer = ObjectUtil.adapt(connectStartEvent, IIncomingFileTransfer.class); |
| final IFileID fileID = connectStartEvent.getFileID(); |
| try |
| { |
| if (fileTransfer != null) |
| { |
| Object httpClient = ReflectUtil.getValue("httpClient", fileTransfer); |
| |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + fileID.getURI() + " managing cookie store for: " + httpClient); |
| } |
| |
| ReflectUtil.setValue("cookieStore", httpClient, new org.apache.http.client.CookieStore() |
| { |
| @SuppressWarnings("all") |
| public List<Cookie> getCookies() |
| { |
| return COOKIE_STORE.getCookies(); |
| } |
| |
| @SuppressWarnings("all") |
| public boolean clearExpired(Date date) |
| { |
| synchronized (COOKIE_STORE) |
| { |
| List<Cookie> originalCookies = new ArrayList<Cookie>(COOKIE_STORE.getCookies()); |
| COOKIE_STORE.clearExpired(date); |
| List<Cookie> remainingCookies = COOKIE_STORE.getCookies(); |
| originalCookies.removeAll(remainingCookies); |
| |
| for (Cookie cookie : originalCookies) |
| { |
| HttpCookie httpCookie = createCookie(cookie); |
| DELEGATING_COOKIE_STORE.basicRemove(null, new HttpCookie(cookie.getName(), cookie.getValue())); |
| } |
| |
| return !originalCookies.isEmpty(); |
| } |
| } |
| |
| @SuppressWarnings("all") |
| public void clear() |
| { |
| COOKIE_STORE.clear(); |
| DELEGATING_COOKIE_STORE.basicRemoveAll(); |
| } |
| |
| @SuppressWarnings("all") |
| public void addCookie(Cookie cookie) |
| { |
| try |
| { |
| java.net.URI uri = fileID.getURI(); |
| HttpCookie httpCookie = createCookie(cookie); |
| DELEGATING_COOKIE_STORE.basicAdd(uri, httpCookie); |
| } |
| catch (Exception ex) |
| { |
| // Ignore bad information. |
| } |
| |
| COOKIE_STORE.addCookie(cookie); |
| } |
| |
| public HttpCookie createCookie(Cookie cookie) |
| { |
| HttpCookie httpCookie = new HttpCookie(cookie.getName(), cookie.getValue()); |
| httpCookie.setDomain(cookie.getDomain()); |
| httpCookie.setPath(cookie.getPath()); |
| httpCookie.setVersion(cookie.getVersion()); |
| return httpCookie; |
| } |
| }); |
| |
| @SuppressWarnings("deprecation") |
| String[] permissiveDatePatterns = ReflectUtil.getValue("DEFAULT_DATE_PATTERNS", org.apache.http.impl.cookie.BrowserCompatSpec.class); |
| |
| try |
| { |
| // The following ensures that more permissive date patterns are used to parse the expiration date of cookies. |
| Object defaultParameters = ReflectUtil.getValue("defaultParams", httpClient); |
| |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + fileID.getURI() + " managing handling handling by modifying date patterns of default params"); |
| } |
| |
| @SuppressWarnings("deprecation") |
| String datePatternsParameterName = org.apache.http.cookie.params.CookieSpecPNames.DATE_PATTERNS; |
| ReflectUtil.invokeMethod(ReflectUtil.getMethod(defaultParameters, "setParameter", String.class, Object.class), defaultParameters, |
| datePatternsParameterName, Arrays.asList(permissiveDatePatterns)); |
| } |
| catch (Throwable throwable2) |
| { |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + fileID.getURI() + " managing cookie handling by modifying the cookie spec providers of the cookie spec registry"); |
| } |
| |
| // This is the case of the ECF implementation based on Apache 4.5. |
| Object copiedSpecRegistry = ReflectUtil.getValue("cookieSpecRegistry", httpClient); |
| ConcurrentHashMap<String, CookieSpecProvider> map = ReflectUtil.getValue("map", copiedSpecRegistry); |
| for (Map.Entry<String, CookieSpecProvider> entry : map.entrySet()) |
| { |
| final CookieSpecProvider cookieSpecProvider = entry.getValue(); |
| if (cookieSpecProvider instanceof org.apache.http.impl.cookie.DefaultCookieSpecProvider) |
| { |
| // Change the date patterns to be permissive. |
| ReflectUtil.setValue("datepatterns", cookieSpecProvider, permissiveDatePatterns); |
| } |
| } |
| } |
| } |
| } |
| catch (Throwable throwable) |
| { |
| if (TRACE) |
| { |
| try |
| { |
| System.out.println("> ECF: " + fileID.getURI() + " failed to manage cookie store"); |
| } |
| catch (URISyntaxException ex) |
| { |
| System.out.println("> ECF: bad fileID URI: " + fileID); |
| } |
| } |
| } |
| } |
| |
| public void await() throws InterruptedException |
| { |
| receiveLatch.await(); |
| } |
| |
| public Exception getException() |
| { |
| return exception; |
| } |
| |
| public boolean hasTransferException() |
| { |
| return exception instanceof IncomingFileTransferException; |
| } |
| |
| public int getErrorCode() |
| { |
| return ((IncomingFileTransferException)exception).getErrorCode(); |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| private static class DelegatingCookieStore implements CookieStore |
| { |
| private final CookieStore delegate = new CookieManager().getCookieStore(); |
| |
| private final org.apache.http.impl.client.BasicCookieStore basicCookieStore; |
| |
| public DelegatingCookieStore(org.apache.http.impl.client.BasicCookieStore basicCookieStore) |
| { |
| this.basicCookieStore = basicCookieStore; |
| } |
| |
| public void add(java.net.URI uri, HttpCookie httpCookie) |
| { |
| basicAdd(uri, httpCookie); |
| basicCookieStore.addCookie(createCookie(uri, httpCookie)); |
| } |
| |
| public void basicAdd(java.net.URI uri, HttpCookie httpCookie) |
| { |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + uri + " adding cookie: " + httpCookie); |
| } |
| |
| delegate.add(uri, httpCookie); |
| } |
| |
| public List<HttpCookie> get(java.net.URI uri) |
| { |
| return delegate.get(uri); |
| } |
| |
| public List<HttpCookie> getCookies() |
| { |
| return delegate.getCookies(); |
| } |
| |
| public List<java.net.URI> getURIs() |
| { |
| return delegate.getURIs(); |
| } |
| |
| public boolean remove(java.net.URI uri, HttpCookie httpCookie) |
| { |
| org.apache.http.impl.cookie.BasicClientCookie basicClientCookie = createCookie(uri, httpCookie); |
| basicClientCookie.setExpiryDate(new Date(System.currentTimeMillis() - 1000)); |
| basicCookieStore.addCookie(basicClientCookie); |
| return basicRemove(uri, httpCookie); |
| } |
| |
| public boolean basicRemove(java.net.URI uri, HttpCookie cookie) |
| { |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + uri + " adding cookie: " + cookie); |
| } |
| |
| return delegate.remove(uri, cookie); |
| } |
| |
| public boolean removeAll() |
| { |
| basicCookieStore.clear(); |
| return basicRemoveAll(); |
| } |
| |
| public boolean basicRemoveAll() |
| { |
| return delegate.removeAll(); |
| } |
| |
| private org.apache.http.impl.cookie.BasicClientCookie createCookie(java.net.URI uri, HttpCookie httpCookie) |
| { |
| org.apache.http.impl.cookie.BasicClientCookie basicClientCookie = new org.apache.http.impl.cookie.BasicClientCookie(httpCookie.getName(), |
| httpCookie.getValue()); |
| basicClientCookie.setPath(httpCookie.getPath()); |
| if (uri != null) |
| { |
| basicClientCookie.setDomain(uri.getHost()); |
| } |
| |
| if (httpCookie.hasExpired()) |
| { |
| basicClientCookie.setExpiryDate(new Date(System.currentTimeMillis() - 1000)); |
| } |
| |
| return basicClientCookie; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return getCookies().toString(); |
| } |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| private static final class RemoteFileSystemListener implements IRemoteFileSystemListener, ConnectionListener |
| { |
| public final CountDownLatch receiveLatch = new CountDownLatch(1); |
| |
| public Exception exception; |
| |
| public IRemoteFileInfo info; |
| |
| public RemoteFileSystemListener() |
| { |
| } |
| |
| public void handleRemoteFileEvent(IRemoteFileSystemEvent event) |
| { |
| if (event instanceof IRemoteFileSystemBrowseEvent) |
| { |
| IRemoteFileSystemBrowseEvent browseEvent = (IRemoteFileSystemBrowseEvent)event; |
| exception = browseEvent.getException(); |
| if (exception == null) |
| { |
| for (IRemoteFile remoteFile : browseEvent.getRemoteFiles()) |
| { |
| info = remoteFile.getInfo(); |
| break; |
| } |
| } |
| |
| receiveLatch.countDown(); |
| } |
| } |
| |
| public void await() throws InterruptedException |
| { |
| receiveLatch.await(); |
| } |
| |
| public Exception getException() |
| { |
| return exception; |
| } |
| |
| public boolean hasTransferException() |
| { |
| return exception instanceof BrowseFileTransferException; |
| } |
| |
| public int getErrorCode() |
| { |
| return ((BrowseFileTransferException)exception).getErrorCode(); |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| public static class ETagMirror extends WorkerPool<ETagMirror, URI, ETagMirror.Worker> |
| { |
| private static final Map<Object, Object> OPTIONS; |
| |
| private static final URIConverter URI_CONVERTER; |
| |
| private static final String OPTION_ETAG_MIRROR = "OPTION_ETAG_MIRROR"; |
| |
| static |
| { |
| ResourceSet resourceSet = SetupCoreUtil.createResourceSet(); |
| OPTIONS = resourceSet.getLoadOptions(); |
| OPTIONS.put(OPTION_CACHE_HANDLING, CacheHandling.CACHE_WITH_ETAG_CHECKING); |
| URI_CONVERTER = resourceSet.getURIConverter(); |
| } |
| |
| private Set<? extends URI> uris; |
| |
| private Map<Object, Object> options = new HashMap<Object, Object>(OPTIONS); |
| |
| public ETagMirror() |
| { |
| options.put(OPTION_ETAG_MIRROR, this); |
| } |
| |
| @Override |
| protected Worker createWorker(URI key, int workerID, boolean secondary) |
| { |
| return new Worker("ETag Mirror " + key, this, key, workerID, secondary); |
| } |
| |
| public void begin(Set<? extends URI> uris, final IProgressMonitor monitor) |
| { |
| options.put(OPTION_MONITOR, monitor); |
| |
| this.uris = uris; |
| int size = uris.size(); |
| monitor.beginTask("Mirroring " + size + " resource" + (size == 1 ? "" : "s"), uris.size()); |
| super.begin("Mirroring", monitor); |
| } |
| |
| @Override |
| protected void run(String taskName, IProgressMonitor monitor) |
| { |
| perform(uris); |
| } |
| |
| protected void cacheUpdated(URI uri) |
| { |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| private static class Worker extends WorkerPool.Worker<URI, ETagMirror> |
| { |
| protected Worker(String name, ETagMirror workPool, URI key, int id, boolean secondary) |
| { |
| super(name, workPool, key, id, secondary); |
| } |
| |
| @Override |
| protected IStatus perform(IProgressMonitor monitor) |
| { |
| URI key = getKey(); |
| ETagMirror workPool = getWorkPool(); |
| IProgressMonitor workpoolMonitor = workPool.getMonitor(); |
| |
| try |
| { |
| workpoolMonitor.subTask("Mirroring " + key); |
| } |
| catch (Exception ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex, IStatus.WARNING); |
| } |
| |
| try |
| { |
| if (TEST_SLOW_NETWORK) |
| { |
| try |
| { |
| Thread.sleep(5000); |
| } |
| catch (InterruptedException ex) |
| { |
| ex.printStackTrace(); |
| } |
| } |
| |
| URI_CONVERTER.createInputStream(key, workPool.options).close(); |
| } |
| catch (IOException ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex, IStatus.WARNING); |
| } |
| finally |
| { |
| try |
| { |
| workpoolMonitor.worked(1); |
| } |
| catch (Exception ex) |
| { |
| SetupCorePlugin.INSTANCE.log(ex, IStatus.WARNING); |
| } |
| } |
| |
| return Status.OK_STATUS; |
| } |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * An interface implemented by {@link FileTransferListener} and {@link RemoteFileSystemListener} for provide common processing support in {@link ConnectionHandler#process()}. |
| */ |
| public interface ConnectionListener |
| { |
| public void await() throws InterruptedException; |
| |
| public Exception getException(); |
| |
| public boolean hasTransferException(); |
| |
| public int getErrorCode(); |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * A handler to unify the common processing logic for {@link ECFURIHandlerImpl#getRemoteAttributes(URI, Map)} and {@link ECFURIHandlerImpl#createInputStream(URI, Map)}. |
| */ |
| private abstract class ConnectionHandler<T> |
| { |
| final protected URI originalURI; |
| |
| protected URI uri; |
| |
| protected final Map<?, ?> options; |
| |
| protected String tracePrefix; |
| |
| protected Authorization forceAuthorization; |
| |
| public ConnectionHandler(URI uri, Map<?, ?> options) throws IOException |
| { |
| this.uri = uri; |
| this.originalURI = uri; |
| this.options = options; |
| } |
| |
| /** |
| * Performs all the processing for the connection and returns the result. |
| */ |
| public T process() throws IOException |
| { |
| // First transform the URI, if necessary, extracting a login URI or form URI if one is needed. |
| Map<Object, Object> transformedOptions = new HashMap<Object, Object>(options); |
| uri = SetupContext.INDEX_SETUP_ARCHIVE_LOCATION_URI.equals(uri) ? ACTUAL_INDEX_SETUP_ARCHIVE_LOCATION_URI : transform(originalURI, transformedOptions); |
| |
| // This is used to prefix all tracing statements. |
| tracePrefix = "> ECF: " + uri; |
| |
| IProgressMonitor monitor = (IProgressMonitor)options.get(OPTION_MONITOR); |
| |
| CountDownLatch countDownLatch = acquireLock(uri); |
| try |
| { |
| if (TEST_IO_EXCEPTION) |
| { |
| File folder = new File(CACHE_FOLDER.toFileString()); |
| if (folder.isDirectory()) |
| { |
| System.out.println("Deleting cache folder: " + folder); |
| IOUtil.deleteBestEffort(folder); |
| } |
| |
| throw new IOException("Simulated network problem: " + uri); |
| } |
| |
| // Setup the basic context for subsequent processing. |
| CacheHandling cacheHandling = getCacheHandling(options); |
| URIConverter uriConverter = getURIConverter(options); |
| URI cacheURI = getCacheFile(uri); |
| String eTag = cacheHandling == CacheHandling.CACHE_IGNORE ? null : getETag(uriConverter, cacheURI); |
| String expectedETag = cacheHandling == CacheHandling.CACHE_IGNORE ? null : getExpectedETag(uri); |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " uri=" + uri); |
| System.out.println(tracePrefix + " cacheURI=" + cacheURI); |
| System.out.println(tracePrefix + " eTag=" + eTag); |
| System.out.println(tracePrefix + " expectedETag=" + expectedETag); |
| } |
| |
| // To prevent Eclipse's Git server from being overload, because it can't scale to thousands of users, we block all direct access. |
| String host = getHost(uri); |
| boolean isBlockedEclipseGitURI = !SetupUtil.SETUP_ARCHIVER_APPLICATION && "git.eclipse.org".equals(host); |
| if (isBlockedEclipseGitURI && uriConverter.exists(cacheURI, options)) |
| { |
| // If the file is in the cache, it's okay to use that cached version, so try that first. |
| cacheHandling = CacheHandling.CACHE_ONLY; |
| } |
| |
| // This is a URI that fails to load at all, so fail quickly. |
| if (FAILED_EXPECTED_ETAG.equals(expectedETag)) |
| { |
| throw EXPECTED_EXCEPTIONS.get(uri); |
| } |
| |
| if (expectedETag != null || cacheHandling == CacheHandling.CACHE_ONLY || cacheHandling == CacheHandling.CACHE_WITHOUT_ETAG_CHECKING) |
| { |
| if (cacheHandling == CacheHandling.CACHE_ONLY || // |
| cacheHandling == CacheHandling.CACHE_WITHOUT_ETAG_CHECKING ? eTag != null : expectedETag.equals(eTag)) |
| { |
| try |
| { |
| return handleCache(uriConverter, cacheURI, expectedETag); |
| } |
| catch (IOException ex) |
| { |
| // Perhaps another JVM is busy writing this file. |
| // Proceed as if it doesn't exist. |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " unable to load cached content"); |
| } |
| } |
| } |
| } |
| |
| // In general all Eclipse-hosted setups should be in the Eclipse project or product catalog and therefore should be in |
| // SetupContext.INDEX_SETUP_ARCHIVE_LOCATION_URI or should already be in the cache from running the setup archiver application. |
| if (isBlockedEclipseGitURI) |
| { |
| synchronized (this) |
| { |
| if (!loggedBlockedURI) |
| { |
| String launcher = OS.getCurrentLauncher(true); |
| if (launcher == null) |
| { |
| launcher = "eclipse"; |
| } |
| |
| // We'll log a single warning for this case. |
| SetupCorePlugin.INSTANCE.log("The Eclipse Git-hosted URI '" + uri + "' is blocked for direct access." + StringUtil.NL + // |
| "Please open a Bugzilla to add it to an official Oomph catalog." + StringUtil.NL + // |
| "For initial testing, use the file system local version of the resource." + StringUtil.NL + // |
| "Alternatively, run the setup archiver application as follows:" + StringUtil.NL + // |
| " " + launcher + " -application org.eclipse.oomph.setup.core.SetupArchiver -consoleLog -noSplash -uris " + uri, // |
| IStatus.WARNING); |
| |
| loggedBlockedURI = true; |
| } |
| } |
| |
| return handleEclipseGit(); |
| } |
| |
| URI loginURI = (URI)transformedOptions.get(OPTION_LOGIN_URI); |
| if (loginURI != null) |
| { |
| try |
| { |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + loginURI + " reading login URI"); |
| } |
| |
| InputStream inputStream = createInputStream(loginURI, options); |
| inputStream.close(); |
| } |
| catch (IOException ex) |
| { |
| // Ignore this. |
| // The main URI should still be attempted. |
| // If it can't get authorization and isn't in the cache, |
| // there will be an appropriate stack trace for that URI. |
| } |
| } |
| |
| // Determine the authorization handler for handling credentials. |
| AuthorizationHandler authorizationHandler = getAuthorizatonHandler(options); |
| |
| URI formURI = (URI)transformedOptions.get(OPTION_FORM_URI); |
| if (formURI != null) |
| { |
| try |
| { |
| if (TRACE) |
| { |
| System.out.println("> ECF: " + formURI + " processing form URI"); |
| } |
| |
| FormHandler formHandler = new FormHandler(formURI, uriConverter, authorizationHandler); |
| formHandler.process(); |
| } |
| catch (IOException ex) |
| { |
| // Ignore this. |
| // The main URI should still be attempted. |
| // If it can't get authorization and isn't in the cache, |
| // there will be an appropriate stack trace for that URI. |
| } |
| } |
| |
| // Encapsulate all the information needed to access and process the URI. |
| ProxyWrapper proxyWrapper = ProxyWrapper.create(uri); |
| |
| // Create the container for the connection. |
| IContainer container = createContainer(); |
| |
| // If we don't have an authorization in the options, but we have a handler, |
| // we might as well get the authorization that might exist in the secure storage so our first access uses the right credentials up front. |
| Authorization authorization = getAuthorizaton(options); |
| if (authorization == null && authorizationHandler != null) |
| { |
| authorization = authorizationHandler.authorize(uri); |
| } |
| |
| // If we are forcing basic authentication... |
| boolean basicAuthentication = Boolean.TRUE.equals(transformedOptions.get(OPTION_BASIC_AUTHENTICATION)); |
| if (basicAuthentication) |
| { |
| // Ensure that we have an authorization, if possible. |
| if ((authorization == null || !authorization.isAuthorized()) && authorizationHandler != null) |
| { |
| authorization = authorizationHandler.reauthorize(uri, authorization); |
| } |
| |
| // Record it so that it's definitely passed in the request header. |
| forceAuthorization = authorization; |
| } |
| |
| // If we don't have a proxy authorization in the options, but we have a handler, |
| // we might as well get the proxy authorization that might exist in the secure storage so our first access uses the right proxy credentials up front. |
| Authorization proxyAuthorization = getProxyAuthorizaton(options); |
| if (proxyWrapper.isProxified() && proxyAuthorization == null && authorizationHandler != null) |
| { |
| proxyAuthorization = authorizationHandler.authorize(proxyWrapper.getProxyURI()); |
| } |
| |
| // If we are using a proxy and it doesn't have an authorization, but we have a proxy authorization that is authorized, |
| // use those credentials for the proxy. |
| if (proxyWrapper.isProxified() && !proxyWrapper.hasAuthorization() && proxyAuthorization != null && proxyAuthorization.isAuthorized()) |
| { |
| proxyWrapper.authorize(proxyAuthorization); |
| } |
| |
| if (TRACE) |
| { |
| if (proxyWrapper.isProxified()) |
| { |
| System.out.println(tracePrefix + " proxy=" + proxyWrapper); |
| } |
| |
| System.out.println(tracePrefix + " authorizationHandler=" + authorizationHandler); |
| } |
| |
| int triedReauthorization = 0; |
| int triedProxyReauthorization = 0; |
| for (int i = 0;; ++i) |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " trying=" + i); |
| System.out.println(tracePrefix + " triedReauthorization=" + triedReauthorization); |
| System.out.println(tracePrefix + " authorization=" + authorization); |
| System.out.println(tracePrefix + " triedProxyReauthorization=" + triedProxyReauthorization); |
| System.out.println(tracePrefix + " proxyAuthorization=" + proxyAuthorization); |
| } |
| |
| // Configure the connection and its associated listener. |
| ConnectionListener transferListener = createConnectionListener(container, proxyWrapper, authorization, eTag, monitor); |
| |
| try |
| { |
| // Start the connection for the URI's associated transfer ID. |
| FileTransferID fileTransferID = new FileTransferID(new FileTransferNamespace(), proxyWrapper.getURI()); |
| sendConnectionRequest(fileTransferID, host); |
| } |
| catch (ECFException ex) |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " " + ex.getClass().getSimpleName()); |
| ex.printStackTrace(System.out); |
| } |
| |
| throw createIOException(uri.toString(), ex); |
| } |
| |
| try |
| { |
| // Wait for the connection processing to complete. |
| transferListener.await(); |
| } |
| catch (InterruptedException ex) |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " InterruptedException"); |
| ex.printStackTrace(System.out); |
| } |
| |
| throw createIOException(uri.toString(), ex); |
| } |
| |
| // Handle any exception captured during the connection processing. |
| Exception exception = transferListener.getException(); |
| if (exception != null) |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " transferLister.exception"); |
| exception.printStackTrace(System.out); |
| } |
| |
| // If it's not a cancel exception... |
| if (!(exception instanceof UserCancelledException)) |
| { |
| // If it's a socked timeout exception, retry the socket 3 times before failing. |
| if ((exception instanceof SocketTimeoutException || exception.getCause() instanceof SocketTimeoutException) && i < 2) |
| { |
| continue; |
| } |
| |
| // If there an authorization handler and the listener's exception is the specialized exception associated with the listener. |
| if (authorizationHandler != null && transferListener.hasTransferException()) |
| { |
| // In this case the exception has an error code. |
| int errorCode = transferListener.getErrorCode(); |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " errorCode=" + errorCode); |
| } |
| |
| // If we have a proxy authentication problem... |
| if (errorCode == HttpURLConnection.HTTP_PROXY_AUTH && proxyWrapper.isProxified()) |
| { |
| // Get the proxy authorization if we don't already have one. |
| if (proxyAuthorization == null) |
| { |
| proxyAuthorization = authorizationHandler.authorize(proxyWrapper.getProxyURI()); |
| |
| // If the proxy is already authorized, apply those credentials to the proxy. |
| if (proxyAuthorization.isAuthorized()) |
| { |
| proxyWrapper.authorize(proxyAuthorization); |
| --i; |
| continue; |
| } |
| } |
| |
| // If the proxy authorization remains authorizable, prompt for the password at most three times. |
| if (!proxyAuthorization.isUnauthorizeable() && triedProxyReauthorization++ < 3) |
| { |
| proxyAuthorization = authorizationHandler.reauthorize(proxyWrapper.getProxyURI(), proxyAuthorization); |
| if (proxyAuthorization.isAuthorized()) |
| { |
| proxyWrapper.authorize(proxyAuthorization); |
| --i; |
| continue; |
| } |
| } |
| } |
| // We assume contents can be accessed via the github API https://developer.github.com/v3/repos/contents/#get-contents |
| // That API, for security reasons, does not return HTTP_UNAUTHORIZED, so we need this special case for that host. |
| else if (errorCode == HttpURLConnection.HTTP_UNAUTHORIZED |
| || API_GITHUB_HOST.equals(getHost(uri)) && errorCode == HttpURLConnection.HTTP_NOT_FOUND |
| || forceAuthorization != null && errorCode == HttpURLConnection.HTTP_FORBIDDEN) |
| { |
| // Get the authorization if we don't already have one. |
| if (authorization == null) |
| { |
| authorization = authorizationHandler.authorize(uri); |
| |
| // If it is authorized, use it now. |
| if (authorization.isAuthorized()) |
| { |
| --i; |
| if (forceAuthorization != null) |
| { |
| forceAuthorization = authorization; |
| } |
| |
| continue; |
| } |
| } |
| |
| // If the authorization remains authorizable, prompt for the password at most three times. |
| if (!authorization.isUnauthorizeable() && triedReauthorization++ < 3) |
| { |
| authorization = authorizationHandler.reauthorize(uri, authorization); |
| if (authorization.isAuthorized()) |
| { |
| --i; |
| if (forceAuthorization != null) |
| { |
| forceAuthorization = authorization; |
| } |
| |
| continue; |
| } |
| } |
| } |
| } |
| |
| if (transferListener.hasTransferException()) |
| { |
| // We can't do a HEAD request. |
| int errorCode = transferListener.getErrorCode(); |
| if (errorCode == HttpURLConnection.HTTP_BAD_METHOD) |
| { |
| T result = handleBadMethod(options); |
| if (result != null) |
| { |
| return result; |
| } |
| } |
| } |
| } |
| |
| if (!CacheHandling.CACHE_IGNORE.equals(cacheHandling) && uriConverter.exists(cacheURI, options) |
| && (!transferListener.hasTransferException() || transferListener.getErrorCode() != HttpURLConnection.HTTP_NOT_FOUND) |
| || uri.equals(ACTUAL_INDEX_SETUP_ARCHIVE_LOCATION_URI) || loginURI != null) |
| { |
| return handleCache(uriConverter, cacheURI, eTag); |
| } |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " failing"); |
| } |
| |
| IOException ioException = createIOException(uri.toString(), transferListener.getException()); |
| EXPECTED_EXCEPTIONS.put(uri, ioException); |
| setExpectedETag(uri, FAILED_EXPECTED_ETAG); |
| |
| throw ioException; |
| } |
| |
| // Saves the credentials for the proxy if they've been authorized at some point during the connection processing. |
| proxyWrapper.update(); |
| |
| return handleResult(uriConverter, cacheURI); |
| } |
| } |
| finally |
| { |
| releaseLock(uri, countDownLatch); |
| } |
| } |
| |
| protected abstract T handleCache(URIConverter uriConverter, URI cacheURI, String expectedETag) throws IOException; |
| |
| protected abstract T handleEclipseGit() throws IOException; |
| |
| protected abstract ConnectionListener createConnectionListener(IContainer container, ProxyWrapper proxyWrapper, Authorization authorization, String eTag, |
| IProgressMonitor monitor); |
| |
| protected abstract void sendConnectionRequest(FileTransferID fileTransferID, String host) throws ECFException; |
| |
| protected T handleBadMethod(Map<?, ?> options) |
| { |
| return null; |
| } |
| |
| protected abstract T handleResult(URIConverter uriConverter, URI cacheURI) throws IOException; |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * A connection handler used by {@link ECFURIHandlerImpl#createInputStream(URI, Map)}. |
| */ |
| private class InputStreamConnectionHandler extends ConnectionHandler<InputStream> |
| { |
| private IRetrieveFileTransferContainerAdapter fileTransfer; |
| |
| private FileTransferListener transferListener; |
| |
| public InputStreamConnectionHandler(URI uri, Map<?, ?> options) throws IOException |
| { |
| super(uri, options); |
| } |
| |
| @Override |
| protected InputStream handleCache(URIConverter uriConverter, URI cacheURI, String expectedETag) throws IOException |
| { |
| setExpectedETag(uri, transferListener == null ? expectedETag |
| : transferListener.eTag == null ? expectedETag == null ? Long.toString(System.currentTimeMillis()) : expectedETag : transferListener.eTag); |
| InputStream result = uriConverter.createInputStream(cacheURI, options); |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " returning cached content"); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected InputStream handleEclipseGit() throws IOException |
| { |
| throw new IOException("Eclipse Git access blocked: " + uri); |
| } |
| |
| @Override |
| protected ConnectionListener createConnectionListener(IContainer container, ProxyWrapper proxyWrapper, Authorization authorization, String eTag, |
| IProgressMonitor monitor) |
| { |
| fileTransfer = ObjectUtil.adapt(container, IRetrieveFileTransferContainerAdapter.class); |
| |
| fileTransfer.setProxy(proxyWrapper.getProxy()); |
| |
| if (authorization != null && authorization.isAuthorized()) |
| { |
| fileTransfer.setConnectContextForAuthentication( |
| ConnectContextFactory.createUsernamePasswordConnectContext(authorization.getUser(), authorization.getPassword())); |
| } |
| |
| transferListener = new FileTransferListener(eTag, monitor); |
| return transferListener; |
| } |
| |
| private void putRequestHeader(Map<Object, Object> requestOptions, String option, String value) |
| { |
| @SuppressWarnings("unchecked") |
| Map<String, String> requestHeaders = (Map<String, String>)requestOptions.get(IRetrieveFileTransferOptions.REQUEST_HEADERS); |
| if (requestHeaders == null) |
| { |
| requestHeaders = new HashMap<String, String>(); |
| requestOptions.put(IRetrieveFileTransferOptions.REQUEST_HEADERS, requestHeaders); |
| } |
| |
| requestHeaders.put(option, value); |
| } |
| |
| @Override |
| protected void sendConnectionRequest(FileTransferID fileTransferID, String host) throws ECFException |
| { |
| Map<Object, Object> requestOptions = new HashMap<Object, Object>(); |
| requestOptions.put(IRetrieveFileTransferOptions.CONNECT_TIMEOUT, CONNECT_TIMEOUT); |
| requestOptions.put("org.eclipse.ecf.provider.filetransfer.httpclient4.retrieve.connectTimeout", CONNECT_TIMEOUT); |
| requestOptions.put(IRetrieveFileTransferOptions.READ_TIMEOUT, READ_TIMEOUT); |
| requestOptions.put("org.eclipse.ecf.provider.filetransfer.httpclient4.retrieve.readTimeout", READ_TIMEOUT); |
| |
| if (!StringUtil.isEmpty(USER_AGENT) && host != null && host.endsWith(".eclipse.org")) |
| { |
| putRequestHeader(requestOptions, "User-Agent", USER_AGENT); |
| } |
| |
| if (forceAuthorization != null) |
| { |
| @SuppressWarnings("unchecked") |
| Map<String, String> requestHeaders = (Map<String, String>)requestOptions.get(IRetrieveFileTransferOptions.REQUEST_HEADERS); |
| if (requestHeaders == null) |
| { |
| requestHeaders = new HashMap<String, String>(); |
| requestOptions.put(IRetrieveFileTransferOptions.REQUEST_HEADERS, requestHeaders); |
| } |
| |
| putRequestHeader(requestOptions, "Authorization", forceAuthorization.getAuthorization()); |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " forcing basic authentication: " + forceAuthorization); |
| } |
| } |
| |
| if (transferListener.expectedETag != null && getCacheHandling(options) != CacheHandling.CACHE_IGNORE) |
| { |
| putRequestHeader(requestOptions, "If-None-Match", transferListener.expectedETag); |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " using If-None-Match : " + transferListener.expectedETag); |
| } |
| } |
| |
| fileTransfer.sendRetrieveRequest(fileTransferID, transferListener, requestOptions); |
| } |
| |
| @Override |
| protected InputStream handleResult(URIConverter uriConverter, URI cacheURI) throws IOException |
| { |
| byte[] bytes = transferListener.out.toByteArray(); |
| |
| // In the case of the Github API, the bytes will be JSON that contains a "content" pair containing the Base64 encoding of the actual contents. |
| if (API_GITHUB_HOST.equals(getHost(uri))) |
| { |
| // Find the start tag in the JSON value. |
| String value = new String(bytes, "UTF-8"); |
| int start = value.indexOf(CONTENT_TAG); |
| if (start != -1) |
| { |
| // Find the ending quote of the encoded contents. |
| start += CONTENT_TAG.length(); |
| int end = value.indexOf('"', start); |
| if (end != -1) |
| { |
| // The content is delimited by \n so split on that during the conversion. |
| String content = value.substring(start, end); |
| String[] split = content.split("\\\\n"); |
| |
| // Write the converted bytes to a new stream and process those bytes instead. |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| for (String line : split) |
| { |
| byte[] binary = XMLTypeFactory.eINSTANCE.createBase64Binary(line); |
| out.write(binary); |
| } |
| |
| out.close(); |
| bytes = out.toByteArray(); |
| } |
| } |
| } |
| |
| try |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " writing cache"); |
| } |
| |
| BaseUtil.writeFile(uriConverter, options, cacheURI, bytes); |
| } |
| catch (IORuntimeException ex) |
| { |
| // Ignore attempts to write out to the cache file. |
| // This may collide with another JVM doing exactly the same thing. |
| transferListener.eTag = null; |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " failed writing cache"); |
| ex.printStackTrace(System.out); |
| } |
| } |
| finally |
| { |
| setETag(uriConverter, cacheURI, transferListener.eTag); |
| } |
| |
| setExpectedETag(uri, transferListener.eTag); |
| Map<Object, Object> response = getResponse(options); |
| if (response != null) |
| { |
| response.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, transferListener.lastModified); |
| } |
| |
| ETagMirror etagMirror = (ETagMirror)options.get(ETagMirror.OPTION_ETAG_MIRROR); |
| if (etagMirror != null) |
| { |
| etagMirror.cacheUpdated(originalURI); |
| } |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " returning successful results"); |
| } |
| |
| return new ByteArrayInputStream(bytes); |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * A connection handler used by {@link ECFURIHandlerImpl#getRemoteAttributes(URI, Map)} |
| */ |
| private class RemoteAttributionsConnectionHandler extends ConnectionHandler<Map<String, ?>> |
| { |
| private IRemoteFileSystemBrowserContainerAdapter fileBrowser; |
| |
| private RemoteFileSystemListener fileSystemListener; |
| |
| private final Set<String> requestedAttributes; |
| |
| public RemoteAttributionsConnectionHandler(URI uri, Map<?, ?> options) throws IOException |
| { |
| super(uri, options); |
| |
| requestedAttributes = getRequestedAttributes(options); |
| } |
| |
| @Override |
| protected Map<String, ?> handleCache(URIConverter uriConverter, URI cacheURI, String expectedETag) throws IOException |
| { |
| Map<String, ?> result = handleAttributes(requestedAttributes, uriConverter.getAttributes(cacheURI, options)); |
| return result; |
| } |
| |
| @Override |
| protected Map<String, ?> handleEclipseGit() throws IOException |
| { |
| return Collections.emptyMap(); |
| } |
| |
| @Override |
| protected ConnectionListener createConnectionListener(IContainer container, ProxyWrapper proxyWrapper, Authorization authorization, String eTag, |
| IProgressMonitor monitor) |
| { |
| fileBrowser = ObjectUtil.adapt(container, IRemoteFileSystemBrowserContainerAdapter.class); |
| |
| fileBrowser.setProxy(proxyWrapper.getProxy()); |
| |
| if (authorization != null && authorization.isAuthorized()) |
| { |
| fileBrowser.setConnectContextForAuthentication( |
| ConnectContextFactory.createUsernamePasswordConnectContext(authorization.getUser(), authorization.getPassword())); |
| } |
| |
| fileSystemListener = new RemoteFileSystemListener(); |
| return fileSystemListener; |
| } |
| |
| @Override |
| protected void sendConnectionRequest(FileTransferID fileTransferID, String host) throws ECFException |
| { |
| fileBrowser.sendBrowseRequest(fileTransferID, fileSystemListener); |
| } |
| |
| @Override |
| protected Map<String, ?> handleResult(URIConverter uriConverter, URI cacheURI) throws IOException |
| { |
| // In the case of the Github API, the bytes will be JSON that contains a "content" pair containing the Base64 encoding of the actual contents. |
| if (API_GITHUB_HOST.equals(getHost(uri))) |
| { |
| // We should have a special case for that too. |
| } |
| |
| return handleAttributes(requestedAttributes, fileSystemListener.info); |
| } |
| |
| @Override |
| protected Map<String, ?> handleBadMethod(Map<?, ?> options) |
| { |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " unsupported HEAD request"); |
| } |
| |
| // Try instead to create an input stream and use the response to get at least the timestamp property. |
| Map<Object, Object> specializedOptions = new HashMap<Object, Object>(options); |
| Map<Object, Object> response = new HashMap<Object, Object>(); |
| specializedOptions.put(URIConverter.OPTION_RESPONSE, response); |
| try |
| { |
| InputStream inputStream = createInputStream(uri, specializedOptions); |
| inputStream.close(); |
| System.out.println(tracePrefix + " using response from GET request"); |
| return handleResponseAttributes(requestedAttributes, response); |
| } |
| catch (IOException ex) |
| { |
| if (TRACE) |
| { |
| // This implies a GET request also fails, so continue the processing... |
| System.out.println(tracePrefix + " GET request failed"); |
| ex.printStackTrace(System.out); |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * https://example.com/gerrit/gitweb?p=EXAMPLE.git;a=blob_plain;f=Src/com.example.releng/example.setup;hb=HEAD |
| */ |
| public static class Main |
| { |
| public void main(String[] args) throws Exception |
| { |
| // TODO |
| // We might need to produce a result that is separated with & rather than ; for some servers? |
| |
| URI expectedURI = URI.createURI("https://example.com/gerrit/gitweb?p=EXAMPLE.git;a=blob_plain;f=Src/com.example.releng/example.setup;hb=HEAD"); |
| URI inputURI = URI.createURI( |
| "https://user:password@example.com:1234/gerrit/gitweb/EXAMPLE.git/Src/com.example.releng/example.setup?oomph=b[0..1];oomph_login=s://a/[0..1]/'login';oomph-p=[2];oomph-f=[3..];a=blob_plain;hb=HEAD;" |
| + "oomph-s=s;" + // |
| "oomph-S=S;" + // |
| "oomph-u=u;" + // |
| "oomph-U=U;" + // |
| "oomph-h=h;" + // |
| "oomph--p=p;" + // |
| "oomph-P=P;" + // |
| "oomph-a=a;" + // |
| "oomph-b=b;" + // |
| "oomph-text='text';" + // |
| "oomph-quote='';" + // |
| "oomph-slash=/;" + // |
| "oomph-colon=:;" + // |
| "oomph-r1=[1];" + // |
| "oomph-r2=[2];" + // |
| "oomph-r3=[-1];" + // |
| "oomph-r4=[-3..-1];" + // |
| "oomph-r5=[-2..]" + // |
| "" // |
| ); |
| |
| inputURI = URI.createURI( |
| "https://example.com/gerrit/gitweb/EXAMPLE.git/Src/com.example.releng/example.setup?oomph=b[0..1];oomph_login=b[0]/'login';oomph-p=[2];oomph-f=[3..];a=blob_plain;hb=HEAD"); |
| new java.net.URI(inputURI.toString()); |
| HashMap<Object, Object> options = new HashMap<Object, Object>(); |
| URI uri = transform(inputURI, options); |
| System.err.println("> " + expectedURI); |
| System.err.println(">> " + inputURI); |
| System.err.println(">>> " + uri); |
| System.err.println(">>>>" + options.get(OPTION_LOGIN_URI)); |
| } |
| |
| private static URI transform(URI uri, Map<Object, Object> options) |
| { |
| String query = uri.query(); |
| if (query != null) |
| { |
| List<String> parameters = StringUtil.explode(query, ";"); |
| if (parameters.isEmpty()) |
| { |
| return uri; |
| } |
| |
| Map<String, String> arguments = new LinkedHashMap<String, String>(); |
| for (String parameter : parameters) |
| { |
| List<String> assignment = StringUtil.explode(parameter, "="); |
| if (assignment.size() != 2) |
| { |
| return uri; |
| } |
| |
| arguments.put(assignment.get(0), assignment.get(1)); |
| } |
| |
| URI outputURI = uri.trimQuery(); |
| URI loginURI = null; |
| URI formURI = null; |
| boolean basicAuthentication = false; |
| Map<String, String> outputQuery = new LinkedHashMap<String, String>(); |
| for (Map.Entry<String, String> entry : arguments.entrySet()) |
| { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| if ("oomph".equals(key)) |
| { |
| String result = evaluate(value, uri); |
| if (result != null) |
| { |
| outputURI = URI.createURI(result); |
| } |
| |
| continue; |
| } |
| else if ("oomph_form".equals(key)) |
| { |
| String result = evaluate(value, uri); |
| if (result != null) |
| { |
| formURI = URI.createURI(result); |
| } |
| |
| continue; |
| } |
| else if ("oomph_login".equals(key)) |
| { |
| String result = evaluate(value, uri); |
| if (result != null) |
| { |
| loginURI = URI.createURI(result); |
| } |
| |
| continue; |
| } |
| else if ("oomph_basic_auth".equals(key)) |
| { |
| basicAuthentication = "true".equals(value); |
| continue; |
| } |
| else if (key.startsWith("oomph-")) |
| { |
| String result = evaluate(value, uri); |
| if (result != null) |
| { |
| key = key.substring("oomph-".length()); |
| value = result; |
| } |
| } |
| |
| outputQuery.put(key, value); |
| } |
| |
| if (!outputQuery.isEmpty()) |
| { |
| StringBuilder queryResult = new StringBuilder(); |
| for (Map.Entry<String, String> entry : outputQuery.entrySet()) |
| { |
| if (queryResult.length() != 0) |
| { |
| queryResult.append(';'); |
| } |
| |
| queryResult.append(entry.getKey()).append('=').append(entry.getValue()); |
| } |
| |
| outputURI = outputURI.appendQuery(queryResult.toString()); |
| } |
| |
| if (loginURI != null && options != null) |
| { |
| options.put(OPTION_LOGIN_URI, loginURI); |
| } |
| |
| if (formURI != null && options != null) |
| { |
| options.put(OPTION_FORM_URI, formURI); |
| } |
| |
| if (basicAuthentication && options != null) |
| { |
| options.put(OPTION_BASIC_AUTHENTICATION, Boolean.TRUE); |
| } |
| |
| return outputURI; |
| } |
| |
| return uri; |
| } |
| |
| // |
| // s -> URI.scheme |
| // S -> URIscheme == null ? "" : URI.scheme ":" |
| // u -> URI.userInfo |
| // U -> URI.userInfo == null ? "" : URI.userInfo "@" |
| // h -> URI.host |
| // p -> URI.port |
| // P -> URI.port == null ? "" : ":" URI.port |
| // a -> URI.authority |
| // b -> URI.scheme ( URI.isHierarchicial ? "//:" URI.authority "/" : ":") |
| // o -> URI.opaqePart |
| // [n{..{m}}] -> URI.segment(n) { m is present ? "/" URI.segment(m) : URI.segments.lastSegment} ** negative n or m -> URI.segmentCount + n |
| // 'text' -> "text" |
| // '' -> "'" |
| // / -> "/" |
| // : -> ":" |
| // * fail |
| // |
| // ^((:?[^\r\n]*(?!property=value)(\r?\n))+)$ |
| // |
| private static String evaluate(String expression, URI uri) |
| { |
| StringBuilder result = new StringBuilder(); |
| boolean quote = false; |
| int segmentCount = uri.segmentCount(); |
| for (int i = 0, length = expression.length(); i < length; ++i) |
| { |
| char character = expression.charAt(i); |
| if (quote && character != '\'' && (character != '%' || i + 2 > length || expression.charAt(i + 1) != '2' || expression.charAt(i + 2) != '7') |
| && (character != '%' || i + 2 > length || expression.charAt(i + 1) != '2' || expression.charAt(i + 2) != '3')) |
| { |
| result.append(character); |
| } |
| else |
| { |
| switch (character) |
| { |
| case '\'': |
| { |
| if (i + 1 < length && expression.charAt(i + 1) == '\'') |
| { |
| ++i; |
| result.append('\''); |
| } |
| else |
| { |
| quote = !quote; |
| } |
| break; |
| } |
| case '%': |
| { |
| if (i + 2 < length && expression.charAt(i + 1) == '2' && expression.charAt(i + 2) == '7') |
| { |
| if (i + 5 < length && expression.charAt(i + 3) == '%' && expression.charAt(i + 4) == '2' && expression.charAt(i + 5) == '7') |
| { |
| i += 5; |
| result.append("%27"); |
| } |
| else |
| { |
| i += 2; |
| quote = !quote; |
| } |
| break; |
| } |
| |
| if (i + 2 < length && expression.charAt(i + 1) == '2' && expression.charAt(i + 2) == '3') |
| { |
| result.append("#"); |
| i += 2; |
| break; |
| } |
| |
| return null; |
| } |
| case 's': |
| { |
| String scheme = uri.scheme(); |
| if (scheme == null) |
| { |
| return null; |
| } |
| result.append(scheme); |
| break; |
| } |
| case 'S': |
| { |
| String scheme = uri.scheme(); |
| if (scheme != null) |
| { |
| result.append(scheme).append(':'); |
| } |
| break; |
| } |
| case 'u': |
| { |
| String userInfo = uri.userInfo(); |
| if (userInfo == null) |
| { |
| return null; |
| } |
| result.append(userInfo); |
| break; |
| } |
| case 'U': |
| { |
| String userInfo = uri.userInfo(); |
| if (userInfo != null) |
| { |
| result.append(userInfo).append('@'); |
| } |
| break; |
| } |
| case 'h': |
| { |
| String host = uri.host(); |
| if (host == null) |
| { |
| return null; |
| } |
| |
| result.append(host); |
| break; |
| } |
| case 'p': |
| { |
| String port = uri.port(); |
| if (port == null) |
| { |
| return null; |
| } |
| result.append(port); |
| break; |
| } |
| case 'P': |
| { |
| String port = uri.port(); |
| if (port != null) |
| { |
| result.append(':').append(port); |
| } |
| break; |
| } |
| case 'a': |
| { |
| String authority = uri.authority(); |
| if (authority == null) |
| { |
| return null; |
| } |
| result.append(authority); |
| break; |
| } |
| case 'b': |
| { |
| String base = uri.isHierarchical() ? uri.trimSegments(segmentCount).trimQuery().trimFragment().toString() : uri.scheme() + ":"; |
| result.append(base); |
| break; |
| } |
| case 'o': |
| { |
| String opaquePart = uri.opaquePart(); |
| if (opaquePart == null) |
| { |
| return null; |
| } |
| result.append(opaquePart); |
| break; |
| } |
| case '/': |
| case ':': |
| { |
| result.append(character); |
| break; |
| } |
| case '[': |
| { |
| if (++i >= length) |
| { |
| return null; |
| } |
| |
| character = expression.charAt(i); |
| boolean negativeN = false; |
| if (character == '-') |
| { |
| negativeN = true; |
| if (++i >= length) |
| { |
| return null; |
| } |
| character = expression.charAt(i); |
| } |
| |
| if (character < '0' && character > '9') |
| { |
| return null; |
| } |
| |
| int n = 0; |
| for (;;) |
| { |
| if (character >= '0' && character <= '9') |
| { |
| n = 10 * n + character - '0'; |
| if (++i >= length) |
| { |
| return null; |
| } |
| character = expression.charAt(i); |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| int m; |
| boolean negativeM = false; |
| if (character == '.') |
| { |
| if (++i >= length) |
| { |
| return null; |
| } |
| |
| character = expression.charAt(i); |
| if (character != '.') |
| { |
| return null; |
| } |
| |
| if (++i >= length) |
| { |
| return null; |
| } |
| |
| character = expression.charAt(i); |
| if (character == '-') |
| { |
| m = 0; |
| negativeM = true; |
| if (++i >= length) |
| { |
| return null; |
| } |
| |
| character = expression.charAt(i); |
| if (character < '0' && character > '9') |
| { |
| return null; |
| } |
| } |
| else if (character >= '0' && character <= '9') |
| { |
| m = 0; |
| } |
| else |
| { |
| m = 1; |
| negativeM = true; |
| } |
| |
| for (;;) |
| { |
| if (character >= '0' && character <= '9') |
| { |
| if (m == Integer.MIN_VALUE) |
| { |
| m = character - '0'; |
| } |
| else |
| { |
| m = 10 * m + character - '0'; |
| } |
| |
| if (++i >= length) |
| { |
| return null; |
| } |
| |
| character = expression.charAt(i); |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| else |
| { |
| m = n; |
| negativeM = negativeN; |
| } |
| |
| if (character != ']') |
| { |
| return null; |
| } |
| |
| if (negativeN) |
| { |
| n = segmentCount - n; |
| } |
| |
| if (negativeM) |
| { |
| m = segmentCount - m; |
| } |
| |
| if (m == Integer.MIN_VALUE) |
| { |
| m = n; |
| } |
| else if (n >= segmentCount || m >= segmentCount || n > m) |
| { |
| return null; |
| } |
| |
| for (int j = n; j <= m; ++j) |
| { |
| if (j != n) |
| { |
| result.append('/'); |
| } |
| |
| result.append(uri.segment(j)); |
| } |
| |
| break; |
| } |
| default: |
| { |
| return null; |
| } |
| } |
| } |
| } |
| |
| if (quote) |
| { |
| return null; |
| } |
| |
| return result.toString(); |
| } |
| } |
| |
| public static URI transform(URI uri, Map<Object, Object> options) |
| { |
| return Main.transform(uri, options); |
| } |
| |
| public static void saveProxies() |
| { |
| if (CommonPlugin.IS_ECLIPSE_RUNNING) |
| { |
| ProxyHelper.saveProxyData(); |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * A wrapper class that holds the ECF proxy, the Core Net proxy data, the URI for that proxy, and the original URI being accessed. |
| */ |
| private static class ProxyWrapper |
| { |
| private Proxy proxy; |
| |
| private final IProxyData proxyData; |
| |
| private final java.net.URI uri; |
| |
| private final URI proxyURI; |
| |
| private boolean authorized; |
| |
| public ProxyWrapper(Proxy proxy, IProxyData proxyData, java.net.URI uri) |
| { |
| this.proxy = proxy; |
| this.proxyData = proxyData; |
| this.uri = uri; |
| |
| if (proxy != null) |
| { |
| ProxyAddress address = proxy.getAddress(); |
| String hostName = address.getHostName(); |
| int port = address.getPort(); |
| Proxy.Type type = proxy.getType(); |
| proxyURI = URI.createURI(type.toString() + "://" + hostName + ":" + port); |
| } |
| else |
| { |
| proxyURI = null; |
| } |
| } |
| |
| /** |
| * Updates the wrapped proxy with the credentials from the authorization. |
| */ |
| public void authorize(Authorization proxyAuthorization) |
| { |
| authorized = true; |
| proxy = new Proxy(proxy.getType(), proxy.getAddress(), proxyAuthorization.getUser(), proxyAuthorization.getPassword()); |
| } |
| |
| /** |
| * Creates a wrapper for the URI being accessed, optionally with proxy information, if it's needed and available. |
| */ |
| public static ProxyWrapper create(URI uri) |
| { |
| java.net.URI javaNetURI = IOUtil.newURI(uri.toString()); |
| if (CommonPlugin.IS_ECLIPSE_RUNNING) |
| { |
| ProxyWrapper proxyWrapper = ProxyHelper.createProxyWrapper(javaNetURI); |
| if (proxyWrapper != null) |
| { |
| return proxyWrapper; |
| } |
| } |
| |
| return new ProxyWrapper(null, null, javaNetURI); |
| } |
| |
| /** |
| * Returns whether this wrapper includes proxy information. |
| */ |
| public boolean isProxified() |
| { |
| return proxy != null; |
| } |
| |
| /** |
| * Returns whether this wrapper includes proxy information that currently includes credentials. |
| */ |
| public boolean hasAuthorization() |
| { |
| return proxy != null && !StringUtil.isEmpty(proxy.getPassword()); |
| } |
| |
| /** |
| * Returns the wrapped ECF proxy. |
| */ |
| public Proxy getProxy() |
| { |
| return proxy; |
| } |
| |
| /** |
| * Returns the URI that can be used to prompt for the proxy's credentials. |
| */ |
| public URI getProxyURI() |
| { |
| return proxyURI; |
| } |
| |
| /** |
| * Returns the URI being processed. |
| */ |
| private java.net.URI getURI() |
| { |
| return uri; |
| } |
| |
| /** |
| * Updates the Core Net proxy data with the credentials of the ECF proxy. |
| */ |
| public void update() |
| { |
| if (authorized) |
| { |
| ProxyHelper.update(proxy, proxyData); |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| if (proxyData == null) |
| { |
| return "Unproxified"; |
| } |
| |
| // This is used for logging. We don't want to show the unobscured password in the log so we don't just use proxyData.toString. |
| StringBuilder stringBuffer = new StringBuilder(); |
| stringBuffer.append("type: "); //$NON-NLS-1$ |
| stringBuffer.append(proxyData.getType()); |
| stringBuffer.append(" host: "); //$NON-NLS-1$ |
| stringBuffer.append(proxyData.getHost()); |
| stringBuffer.append(" port: "); //$NON-NLS-1$ |
| stringBuffer.append(proxyData.getPort()); |
| stringBuffer.append(" user: "); //$NON-NLS-1$ |
| stringBuffer.append(proxyData.getUserId()); |
| stringBuffer.append(" password: "); //$NON-NLS-1$ |
| String password = proxyData.getPassword(); |
| stringBuffer.append(password == null ? "" : XMLTypeFactory.eINSTANCE.convertBase64Binary(password.getBytes())); |
| stringBuffer.append(" reqAuth: "); //$NON-NLS-1$ |
| stringBuffer.append(proxyData.isRequiresAuthentication()); |
| return stringBuffer.toString(); |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| * |
| * This helper class is used only when Eclipse is running in which case the Core Net bundle should be available. |
| */ |
| protected static class ProxyHelper |
| { |
| private static final Set<IProxyData> PROXY_DATA = new HashSet<IProxyData>(); |
| |
| @SuppressWarnings("restriction") |
| private static final IProxyService PROXY_MANAGER = org.eclipse.core.internal.net.ProxyManager.getProxyManager(); |
| |
| /** |
| * Creates a wrapper using ECF and the Core Net infrastructure. |
| */ |
| public static ProxyWrapper createProxyWrapper(java.net.URI uri) |
| { |
| if (PROXY_MANAGER.isProxiesEnabled()) |
| { |
| synchronized (PROXY_MANAGER) |
| { |
| // This replicates the logic in ProxySetupHelper.getProxy so that we can also record the proxy data from which the proxy is created. |
| final IProxyData[] proxies = PROXY_MANAGER.select(uri); |
| IProxyData selectedProxy = ProxySetupHelper.selectProxyFromProxies(uri.getScheme(), proxies); |
| if (selectedProxy != null) |
| { |
| Proxy proxy = new Proxy(selectedProxy.getType().equalsIgnoreCase(IProxyData.SOCKS_PROXY_TYPE) ? Proxy.Type.SOCKS : Proxy.Type.HTTP, |
| new ProxyAddress(selectedProxy.getHost(), selectedProxy.getPort()), selectedProxy.getUserId(), selectedProxy.getPassword()); |
| return new ProxyWrapper(proxy, selectedProxy, uri); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Updates the proxy data with the credentials of the ECF proxy. |
| */ |
| protected static void update(Proxy proxy, IProxyData proxyData) |
| { |
| if (SetupUtil.INSTALLER_APPLICATION || SetupUtil.SETUP_ARCHIVER_APPLICATION) |
| { |
| synchronized (PROXY_MANAGER) |
| { |
| proxyData.setUserid(proxy.getUsername()); |
| proxyData.setPassword(proxy.getPassword()); |
| PROXY_DATA.add(proxyData); |
| } |
| } |
| } |
| |
| /** |
| * Saves all the proxy data instances that have been used. |
| * This is useful when the system proxies require authentication and the password has been prompted from the user. |
| * In this case, we can switch the mode to Manual and save the proxies with their credentials so subsequent uses of the installer can reuse those saved credentials. |
| */ |
| protected static void saveProxyData() |
| { |
| if (!PROXY_DATA.isEmpty()) |
| { |
| synchronized (PROXY_MANAGER) |
| { |
| try |
| { |
| PROXY_MANAGER.setProxyData(PROXY_DATA.toArray(new IProxyData[PROXY_DATA.size()])); |
| PROXY_MANAGER.setSystemProxiesEnabled(false); |
| |
| // This forces the preferences to be flushed. |
| PROXY_MANAGER.setProxiesEnabled(false); |
| PROXY_MANAGER.setProxiesEnabled(true); |
| } |
| catch (CoreException ex) |
| { |
| // Ignore. |
| } |
| } |
| |
| PROXY_DATA.clear(); |
| } |
| } |
| } |
| |
| /** |
| * |
| * @author Ed Merks |
| */ |
| public static class FormHandler |
| { |
| private static final Map<URI, Authorization> AUTHORIZED_FORMS = Collections.synchronizedMap(new HashMap<URI, Authorization>()); |
| |
| private static final Pattern FORM_PATTERN = Pattern.compile("<\\s*form\\s*([^>]*)>(.*?)</\\s*form\\s*>", Pattern.DOTALL); |
| |
| private static final Pattern INPUT_ELEMENT_PATTERN = Pattern.compile("<\\s*input\\s*([^>]*)>"); |
| |
| private static final Pattern ACTION_ATTRIBUTE_PATTERN = getAttributePattern("action"); |
| |
| private static final Pattern ID_ATTRIBUTE_PATTERN = getAttributePattern("id"); |
| |
| private static final Pattern NAME_ATTRIBUTE_PATTERN = getAttributePattern("name"); |
| |
| private static final Pattern TYPE_ATTRIBUTE_PATTERN = getAttributePattern("type"); |
| |
| private static final Pattern VALUE_ATTRIBUTE_PATTERN = getAttributePattern("value"); |
| |
| private static final Pattern HEX_ENTITY_PATTERN = Pattern.compile("&#x([0-9]+);"); |
| |
| private final URI formURI; |
| |
| private final URIConverter uriConverter; |
| |
| private final Map<String, String> parameterTypes = new LinkedHashMap<String, String>(); |
| |
| private final Map<String, String> parameterValues = new LinkedHashMap<String, String>(); |
| |
| private final Set<String> secureParameters = new LinkedHashSet<String>(); |
| |
| private final AuthorizationHandler authorizationHandler; |
| |
| public FormHandler(URI formURI, URIConverter uriConverter, AuthorizationHandler authorizationHandler) |
| { |
| this.formURI = formURI; |
| this.uriConverter = uriConverter; |
| this.authorizationHandler = authorizationHandler; |
| } |
| |
| public boolean process() throws IOException |
| { |
| boolean result = false; |
| if (authorizationHandler != null) |
| { |
| Authorization authorization = authorizationHandler.authorize(formURI); |
| URI formLockURI = URI.createURI("form:" + formURI); |
| CountDownLatch countDownLatch = acquireLock(formLockURI); |
| try |
| { |
| // If we've previously successfully authorized with the same credentials... |
| if (authorization.equals(AUTHORIZED_FORMS.get(formLockURI))) |
| { |
| if (!isRedo()) |
| { |
| // Return here so that we don't needlessly put the same authorization back the map. |
| return true; |
| } |
| |
| java.net.URI javaNetFormURI; |
| try |
| { |
| javaNetFormURI = new java.net.URI(formURI.toString()); |
| } |
| catch (URISyntaxException ex) |
| { |
| throw new IOExceptionWithCause(ex); |
| } |
| |
| for (HttpCookie httpCookie : COOKIE_STORE.get(javaNetFormURI)) |
| { |
| COOKIE_STORE.remove(javaNetFormURI, httpCookie); |
| } |
| } |
| |
| URI formActionURI = readForm(formURI); |
| |
| if (!secureParameters.isEmpty() && formActionURI != null) |
| { |
| int limit = 3; |
| if (!authorization.isAuthorized()) |
| { |
| --limit; |
| authorization = authorizationHandler.reauthorize(formURI, authorization); |
| } |
| |
| Set<String> emptyKeys = new HashSet<String>(); |
| for (int i = 0; i < limit && !authorization.isUnauthorizeable(); ++i) |
| { |
| if (authorization.isAuthorized()) |
| { |
| for (Map.Entry<String, String> entry : parameterValues.entrySet()) |
| { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| if (StringUtil.isEmpty(value) || emptyKeys.contains(key)) |
| { |
| String type = parameterTypes.get(key); |
| if ("password".equals(type)) |
| { |
| parameterValues.put(key, PreferencesUtil.encrypt(authorization.getPassword())); |
| } |
| else |
| { |
| emptyKeys.add(key); |
| parameterValues.put(key, authorization.getUser()); |
| } |
| } |
| } |
| |
| if (postForm(formActionURI)) |
| { |
| result = true; |
| break; |
| } |
| |
| authorization = authorizationHandler.reauthorize(formURI, authorization); |
| } |
| } |
| } |
| } |
| finally |
| { |
| if (result) |
| { |
| AUTHORIZED_FORMS.put(formLockURI, authorization); |
| } |
| |
| releaseLock(formLockURI, countDownLatch); |
| } |
| } |
| |
| return result; |
| } |
| |
| protected boolean isRedo() |
| { |
| return false; |
| } |
| |
| private URI readForm(URI formURI) throws IOException |
| { |
| InputStream in = null; |
| ByteArrayOutputStream out = null; |
| try |
| { |
| String tracePrefix = "> ECF: " + formURI; |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " reading form"); |
| } |
| |
| // Ensure that the input stream really reads the remote form, not something from the cache. |
| // This is to ensure that the cookies of the response header are properly processed. |
| HashMap<Object, Object> options = new HashMap<Object, Object>(); |
| options.put(OPTION_CACHE_HANDLING, CacheHandling.CACHE_IGNORE); |
| options.put(OPTION_AUTHORIZATION_HANDLER, null); |
| in = uriConverter.createInputStream(formURI, options); |
| |
| // Copy the result into memory and convert it to string. |
| out = new ByteArrayOutputStream(); |
| IOUtil.copy(in, out); |
| String contents = out.toString("UTF-8"); |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " finding form contents"); |
| } |
| |
| String formID = formURI.fragment(); |
| |
| // Look for the form. |
| for (Matcher formMatcher = FORM_PATTERN.matcher(contents); formMatcher.find();) |
| { |
| parameterValues.clear(); |
| parameterTypes.clear(); |
| |
| String formElement = formMatcher.group(1); |
| String id = getAttributeValue(ID_ATTRIBUTE_PATTERN, formElement); |
| if (formID != null && !formID.equals(id)) |
| { |
| continue; |
| } |
| |
| // The form must have an action attribute. |
| String action = getAttributeValue(ACTION_ATTRIBUTE_PATTERN, formElement); |
| if (action != null) |
| { |
| // This is the URI to which we must post the form data. |
| URI formActionURI = URI.createURI(action).resolve(formURI); |
| if (formActionURI != null) |
| { |
| String formContents = formMatcher.group(2); |
| |
| // Look for all the input elements of the form. |
| for (Matcher inputMatcher = INPUT_ELEMENT_PATTERN.matcher(formContents); inputMatcher.find();) |
| { |
| String inputElement = inputMatcher.group(1); |
| |
| String name = getAttributeValue(NAME_ATTRIBUTE_PATTERN, inputElement); |
| String type = getAttributeValue(TYPE_ATTRIBUTE_PATTERN, inputElement); |
| String value = getAttributeValue(VALUE_ATTRIBUTE_PATTERN, inputElement); |
| |
| // Record the values of all well-formed inputs. |
| if (name != null && type != null) |
| { |
| parameterValues.put(name, value == null ? "" : value); |
| parameterTypes.put(name, type); |
| if ("password".equals(type)) |
| { |
| secureParameters.add(name); |
| } |
| } |
| } |
| } |
| |
| if (!secureParameters.isEmpty()) |
| { |
| if (TRACE) |
| { |
| StringBuilder details = new StringBuilder(); |
| for (Map.Entry<String, String> entry : parameterValues.entrySet()) |
| { |
| details.append(StringUtil.NL).append(" ").append(entry.getKey()).append("='").append(entry.getValue()).append("' type=") |
| .append(parameterTypes.get(entry.getKey())); |
| } |
| |
| System.out.println(tracePrefix + " form parameters " + details); |
| } |
| return formActionURI; |
| } |
| } |
| } |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " form contents not found"); |
| } |
| |
| return null; |
| } |
| finally |
| { |
| IOUtil.closeSilent(in); |
| } |
| } |
| |
| private boolean postForm(URI formActionURI) throws IOException |
| { |
| boolean result = false; |
| URL url = new URL(formActionURI.toString()); |
| |
| String tracePrefix = "> ECF: " + formActionURI; |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " posting form"); |
| } |
| |
| // Establish a connection that looks just like a browser connection to post the form data. |
| HttpURLConnection connection = (HttpURLConnection)url.openConnection(); |
| |
| connection.setUseCaches(false); |
| connection.setConnectTimeout(5000); |
| connection.setRequestMethod("POST"); |
| connection.setRequestProperty("User-Agent", "Mozilla/5.0"); |
| connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); |
| connection.setDoOutput(true); |
| connection.setInstanceFollowRedirects(false); |
| |
| java.net.URI uri; |
| try |
| { |
| uri = url.toURI(); |
| } |
| catch (URISyntaxException ex) |
| { |
| throw new IOExceptionWithCause(ex); |
| } |
| |
| // Be sure to add cookies, e.g., any cookies returned when the form was read earlier. |
| List<HttpCookie> cookies = COOKIE_STORE.get(uri); |
| for (HttpCookie httpCookie : cookies) |
| { |
| connection.addRequestProperty("Cookie", httpCookie.toString().replace("\"", "")); |
| } |
| |
| if (TRACE) |
| { |
| StringBuilder details = new StringBuilder(); |
| for (HttpCookie httpCookie : cookies) |
| { |
| details.append(StringUtil.NL).append(" ").append(httpCookie); |
| } |
| |
| System.out.println(tracePrefix + (details.length() == 0 ? " using no cookies" : " using cookies" + details)); |
| } |
| |
| handleProxy(connection); |
| |
| // Write the form data to connection. |
| DataOutputStream data = new DataOutputStream(connection.getOutputStream()); |
| String form = getForm(parameterValues, secureParameters, true); |
| |
| if (TRACE) |
| { |
| System.out.println(tracePrefix + " posting parameters" + StringUtil.NL + " " + getForm(parameterValues, secureParameters, false)); |
| } |
| |
| data.writeBytes(form); |
| data.close(); |
| |
| // If we receive a valid response... |
| int responseCode = connection.getResponseCode(); |
| if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) |
| { |
| // Look for the cookies... |
| Map<String, List<String>> headerFields = connection.getHeaderFields(); |
| List<String> list = headerFields.get("Set-Cookie"); |
| if (list != null) |
| { |
| for (String value : list) |
| { |
| List<HttpCookie> httpCookies = HttpCookie.parse(value); |
| for (HttpCookie httpCookie : httpCookies) |
| { |
| httpCookie.setDomain(getHost(uri)); |
| |
| // Some sites serve up bogus expiry information that results in the cookie not being added to the store. |
| if (httpCookie.getMaxAge() == 0) |
| { |
| httpCookie.setMaxAge(-1); |
| } |
| |
| ECFURIHandlerImpl.COOKIE_STORE.add(uri, httpCookie); |
| result = true; |
| } |
| } |
| } |
| |
| // Allow subclasses to also inspect the header fields. |
| if (result) |
| { |
| result = validHeaders(headerFields); |
| } |
| } |
| |
| return result; |
| } |
| |
| protected boolean validHeaders(Map<String, List<String>> headers) |
| { |
| return true; |
| } |
| |
| private static String getForm(Map<String, String> parameters, Collection<String> secureKeys, boolean secure) |
| { |
| StringBuilder form = new StringBuilder(); |
| for (Map.Entry<String, String> entry : parameters.entrySet()) |
| { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| if (secure && secureKeys != null && secureKeys.contains(key)) |
| { |
| value = PreferencesUtil.decrypt(value); |
| } |
| |
| if (form.length() != 0) |
| { |
| form.append('&'); |
| } |
| |
| form.append(key); |
| form.append('='); |
| try |
| { |
| form.append(URLEncoder.encode(value, "UTF-8")); |
| } |
| catch (UnsupportedEncodingException ex) |
| { |
| // UTF-8 is always supported. |
| } |
| } |
| |
| return form.toString(); |
| } |
| |
| private void handleProxy(HttpURLConnection connection) |
| { |
| try |
| { |
| // If there are proxy settings, ensure that the proper proxy authorization is established. |
| Proxy proxy = ProxySetupHelper.getProxy(formURI.toString()); |
| if (proxy != null) |
| { |
| Authorization authorization = new ECFURIHandlerImpl.AuthorizationHandler.Authorization(proxy.getUsername(), proxy.getPassword()); |
| if (authorization.isAuthorized()) |
| { |
| connection.setRequestProperty("Proxy-Authorization", authorization.getAuthorization()); |
| } |
| } |
| } |
| catch (NoClassDefFoundError ex) |
| { |
| // Ignore. |
| } |
| } |
| |
| private static Pattern getAttributePattern(String attributeName) |
| { |
| return Pattern.compile("\\s*" + attributeName + "\\s*=\\s*(\"[^\"]*\"|'[^']*')"); |
| } |
| |
| private static String getAttributeValue(Pattern attributePattern, String attributes) |
| { |
| Matcher matcher = attributePattern.matcher(attributes); |
| if (matcher.find()) |
| { |
| String quotedValue = matcher.group(1); |
| for (Matcher entityMatcher = HEX_ENTITY_PATTERN.matcher(quotedValue); entityMatcher.find();) |
| { |
| quotedValue = quotedValue.replace(entityMatcher.group(), new String(Character.toChars(Integer.valueOf(entityMatcher.group(1), 16)))); |
| } |
| return quotedValue.substring(1, quotedValue.length() - 1); |
| } |
| return null; |
| } |
| } |
| } |