diff options
author | Henrik Lindberg | 2009-08-26 21:15:09 +0000 |
---|---|---|
committer | Henrik Lindberg | 2009-08-26 21:15:09 +0000 |
commit | 600e890188a495ca44180163f1d93616fb88ecb0 (patch) | |
tree | 1fea25dde2fdb725816635c13ae10e05e5535234 /bundles/org.eclipse.equinox.p2.repository | |
parent | 3857e9aa964cc8fe33a211c97ac3649d8d6fb981 (diff) | |
download | rt.equinox.p2-600e890188a495ca44180163f1d93616fb88ecb0.tar.gz rt.equinox.p2-600e890188a495ca44180163f1d93616fb88ecb0.tar.xz rt.equinox.p2-600e890188a495ca44180163f1d93616fb88ecb0.zip |
Bug 287558 - Adopt ECF support on NTLM2 (part 1)
Added support for the new ECF status code 477 - after this checkin a new version of ECF that generates 477 status code can be used - work remains to actually switch http client when this occurs.
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.repository')
9 files changed, 568 insertions, 79 deletions
diff --git a/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF index d55cba84f..4cf5a1439 100644 --- a/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF @@ -20,6 +20,7 @@ Import-Package: javax.xml.parsers, org.eclipse.equinox.internal.p2.repository.helpers, org.eclipse.equinox.internal.provisional.p2.core, org.eclipse.equinox.security.storage, + org.eclipse.osgi.service.debug, org.eclipse.osgi.util;version="1.1.0", org.osgi.framework;version="1.4.0", org.osgi.service.packageadmin;version="1.2.0", diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Activator.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Activator.java index fe8774eb8..1837a17c7 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Activator.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Activator.java @@ -62,6 +62,12 @@ public class Activator implements BundleActivator { return (IRetrieveFileTransferFactory) getFileTransferServiceTracker().getService(); } + public synchronized void useJREHttpClient() { + // TODO: Check of JREHttpClient is already in use - then do nothing, else switch to JRE HTTP Client + // + // TODO - Log that the http client was switched. + } + /** * Gets the singleton ServiceTracker for the IRetrieveFileTransferFactory and starts the bundles * "org.eclipse.ecf" and diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Credentials.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Credentials.java index 7fcceb6a6..0ff55aac9 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Credentials.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Credentials.java @@ -19,6 +19,7 @@ import java.util.*; import org.eclipse.core.runtime.*; import org.eclipse.ecf.filetransfer.UserCancelledException; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.p2.repository.helpers.DebugHelper; import org.eclipse.equinox.internal.provisional.p2.core.IServiceUI; import org.eclipse.equinox.internal.provisional.p2.core.IServiceUI.AuthenticationInfo; import org.eclipse.equinox.internal.provisional.p2.repository.IRepository; @@ -35,9 +36,23 @@ public class Credentials { } + /** + * Cache of auth information that is not persisted, and modified auth info. + */ private static final Map savedAuthInfo = Collections.synchronizedMap(new HashMap()); /** + * Information about retry counts, and prompts canceled by user. The SoftReference is + * a Map if not null. The keys are also used as serialization per host. + */ + private static Map remembered; + + /** + * Serializes pop up of login/password prompt + */ + private static final Object promptLock = new Object(); + + /** * Returns the AuthenticationInfo for the given URI. This may prompt the * user for user name and password as required. * @@ -76,23 +91,7 @@ public class Credentials { * @throws CoreException if there is an error */ public static AuthenticationInfo forLocation(URI location, boolean prompt, AuthenticationInfo lastUsed) throws LoginCanceledException, CoreException { - ISecurePreferences securePreferences = SecurePreferencesFactory.getDefault(); - - // if URI is not opaque, just getting the host may be enough - String host = location.getHost(); - if (host == null) { - String scheme = location.getScheme(); - if (URIUtil.isFileURI(location) || scheme == null) - // If the URI references a file, a password could possibly be needed for the directory - // (it could be a protected zip file representing a compressed directory) - in this - // case the key is the path without the last segment. - // Using "Path" this way may result in an empty string - which later will result in - // an invalid key. - host = new Path(location.toString()).removeLastSegments(1).toString(); - else - // it is an opaque URI - details are unknown - can only use entire string. - host = location.toString(); - } + String host = uriToHost(location); String nodeKey; try { nodeKey = URLEncoder.encode(host, "UTF-8"); //$NON-NLS-1$ @@ -109,69 +108,214 @@ public class Credentials { throw RepositoryStatusHelper.internalError(e); } } - String nodeName = IRepository.PREFERENCE_NODE + '/' + nodeKey; - ISecurePreferences prefNode = null; - try { - if (securePreferences.nodeExists(nodeName)) - prefNode = securePreferences.node(nodeName); - } catch (IllegalArgumentException e) { - // if the node name is illegal/malformed (should not happen). - throw RepositoryStatusHelper.internalError(e); - } catch (IllegalStateException e) { - // thrown if preference store has been tampered with - throw RepositoryStatusHelper.internalError(e); + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:ENTER", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ } - if (!prompt) { - try { - if (prefNode != null) { - String username = prefNode.get(IRepository.PROP_USERNAME, null); - String password = prefNode.get(IRepository.PROP_PASSWORD, null); - // if we don't have stored connection data just return a null auth info - if (username != null && password != null) - return new IServiceUI.AuthenticationInfo(username, password, true); - } - return restoreFromMemory(nodeName); - } catch (StorageException e) { - throw RepositoryStatusHelper.internalError(e); + + // Must serialize getting stored permissions per host as the location may + // be prompted right now + // Start by getting a key to lock on + HostEntry hostLock = null; + synchronized (Credentials.class) { + Map r = getRemembered(); + hostLock = (HostEntry) r.get(host); + if (hostLock == null) { + hostLock = new HostEntry(0); + r.put(host, hostLock); } } - //need to prompt user for user name and password - IServiceUI adminUIService = (IServiceUI) ServiceHelper.getService(Activator.getContext(), IServiceUI.class.getName()); AuthenticationInfo loginDetails = null; - if (adminUIService != null) - loginDetails = lastUsed != null ? adminUIService.getUsernamePassword(host, lastUsed) : adminUIService.getUsernamePassword(host); - //null result means user canceled password dialog - if (loginDetails == null) - throw new LoginCanceledException(); - //save user name and password if requested by user - if (loginDetails.saveResult()) { - if (prefNode == null) - prefNode = securePreferences.node(nodeName); + ISecurePreferences securePreferences = null; + // synchronize getting secure store with prompting user, as it may prompt. + synchronized (promptLock) { + securePreferences = SecurePreferencesFactory.getDefault(); + } + + // serialize the prompting per host + synchronized (hostLock) { try { - prefNode.put(IRepository.PROP_USERNAME, loginDetails.getUserName(), true); - prefNode.put(IRepository.PROP_PASSWORD, loginDetails.getPassword(), true); - prefNode.flush(); - } catch (StorageException e1) { - throw RepositoryStatusHelper.internalError(e1); - } catch (IOException e) { - throw RepositoryStatusHelper.internalError(e); - } - } else { - // if persisted earlier - the preference should be removed - if (securePreferences.nodeExists(nodeName)) { - prefNode = securePreferences.node(nodeName); - prefNode.removeNode(); + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:HOSTLOCK OBTAINED", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ + } + + String nodeName = IRepository.PREFERENCE_NODE + '/' + nodeKey; + ISecurePreferences prefNode = null; try { - prefNode.flush(); - } catch (IOException e) { + if (securePreferences.nodeExists(nodeName)) + prefNode = securePreferences.node(nodeName); + } catch (IllegalArgumentException e) { + // if the node name is illegal/malformed (should not happen). + throw RepositoryStatusHelper.internalError(e); + } catch (IllegalStateException e) { + // thrown if preference store has been tampered with throw RepositoryStatusHelper.internalError(e); } + if (!prompt) { + try { + if (prefNode != null) { + String username = prefNode.get(IRepository.PROP_USERNAME, null); + String password = prefNode.get(IRepository.PROP_PASSWORD, null); + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + if (username != null && password != null) { + DebugHelper.debug("Credentials", "forLocation:PREFNODE FOUND - USING STORED INFO", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + // if we don't have stored connection data just return a null auth info + if (username != null && password != null) + return new IServiceUI.AuthenticationInfo(username, password, true); + } + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:PREFNODE NOT FOUND - RETURN FROM MEMORY", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ + } + return restoreFromMemory(nodeName); + } catch (StorageException e) { + throw RepositoryStatusHelper.internalError(e); + } + } + // need to prompt user for user name and password + // first check (throw exception) if having a remembered cancel + checkRememberedCancel(host); + + // check if another thread has modified the credentials since last attempt + // made by current thread - if so, try with latest without prompting + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + AuthenticationInfo latest = restoreFromMemory(nodeName); + boolean useLatest = false; + if (latest != null && lastUsed != null) + if (!(latest.getUserName().equals(lastUsed.getUserName()) && latest.getPassword().equals(lastUsed.getPassword()))) + useLatest = true; + if (useLatest) + DebugHelper.debug("Credentials", "forLocation:LATER INFO AVAILABLE - RETURNING IT", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ + } + + AuthenticationInfo latest = restoreFromMemory(nodeName); + if (latest != null && lastUsed != null) + if (!(latest.getUserName().equals(lastUsed.getUserName()) && latest.getPassword().equals(lastUsed.getPassword()))) + return latest; + + // check if number of prompts have been exceeded for the host - if so + // do a synthetic Login canceled by user + // (The alternative is to return "latest" until retry login gives up with + // authentication failed - but that would waste time). + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + if (getPromptCount(host) >= RepositoryPreferences.getLoginRetryCount()) { + if (lastUsed == null && latest == null) + DebugHelper.debug("Credentials", "forLocation:NO INFO - SYNTHETIC CANCEL", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + return latest == null ? lastUsed : latest; // keep client failing on the latest known + } + DebugHelper.debug("Credentials", "forLocation:LATER INFO AVAILABLE - RETURNING IT", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location, "prompt", Boolean.toString(prompt)}); //$NON-NLS-1$ //$NON-NLS-2$ + + } + if (getPromptCount(host) >= RepositoryPreferences.getLoginRetryCount()) { + if (lastUsed == null && latest == null) + throw new LoginCanceledException(); + return latest == null ? lastUsed : latest; // keep client failing on the latest known + } + IServiceUI adminUIService = (IServiceUI) ServiceHelper.getService(Activator.getContext(), IServiceUI.class.getName()); + if (adminUIService != null) + synchronized (promptLock) { + try { + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:PROMPTLOCK OBTAINED", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } + + // serialize the popping of the dialog itself + loginDetails = lastUsed != null ? adminUIService.getUsernamePassword(host, lastUsed) : adminUIService.getUsernamePassword(host); + //null result means user canceled password dialog + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + if (loginDetails == null) + DebugHelper.debug("Credentials", "forLocation:PROMPTED - USER CANCELED (PROMPT LOCK RELEASED)", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } + if (loginDetails == null) { + rememberCancel(host); + throw new LoginCanceledException(); + } + //save user name and password if requested by user + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + if (loginDetails.saveResult()) + DebugHelper.debug("Credentials", "forLocation:SAVING RESULT", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } + + if (loginDetails.saveResult()) { + if (prefNode == null) + prefNode = securePreferences.node(nodeName); + try { + prefNode.put(IRepository.PROP_USERNAME, loginDetails.getUserName(), true); + prefNode.put(IRepository.PROP_PASSWORD, loginDetails.getPassword(), true); + prefNode.flush(); + } catch (StorageException e1) { + throw RepositoryStatusHelper.internalError(e1); + } catch (IOException e) { + throw RepositoryStatusHelper.internalError(e); + } + } else { + // if persisted earlier - the preference should be removed + if (securePreferences.nodeExists(nodeName)) { + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:REMOVING PREVIOUSLY SAVED INFO", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } + + prefNode = securePreferences.node(nodeName); + prefNode.removeNode(); + try { + prefNode.flush(); + } catch (IOException e) { + throw RepositoryStatusHelper.internalError(e); + } + } + } + saveInMemory(nodeName, loginDetails); + } finally { + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:PROMPTLOCK RELEASED", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } + } + } + incrementPromptCount(host); + } finally { + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "forLocation:HOSTLOCK RELEASED", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", location}); //$NON-NLS-1$ + } } - saveInMemory(nodeName, loginDetails); + } + return loginDetails; } + private static String uriToHost(URI location) { + // if URI is not opaque, just getting the host may be enough + String host = location.getHost(); + if (host == null) { + String scheme = location.getScheme(); + if (URIUtil.isFileURI(location) || scheme == null) + // If the URI references a file, a password could possibly be needed for the directory + // (it could be a protected zip file representing a compressed directory) - in this + // case the key is the path without the last segment. + // Using "Path" this way may result in an empty string - which later will result in + // an invalid key. + host = new Path(location.toString()).removeLastSegments(1).toString(); + else + // it is an opaque URI - details are unknown - can only use entire string. + host = location.toString(); + } + return host; + } + /** * Returns authentication details stored in memory for the given node name, * or <code>null</code> if no information is stored. @@ -181,10 +325,158 @@ public class Credentials { } /** - * Saves authentication details in memory so user is only prompted once per session + * Saves authentication details in memory so user is only prompted once per (SDK) session */ private static void saveInMemory(String nodeName, AuthenticationInfo loginDetails) { savedAuthInfo.put(nodeName, loginDetails); } + /** + * Remember the fact that the host was canceled. + * @param host + */ + private static void rememberCancel(String host) { + Map r = getRemembered(); + if (r != null) + r.put(host, new HostEntry(-1)); + } + + /** + * Throws LoginCancledException if the host was previously canceled, and the information + * is not stale. + * @param host + * @throws LoginCanceledException + */ + private static void checkRememberedCancel(String host) throws LoginCanceledException { + Map r = getRemembered(); + if (r != null) { + Object x = r.get(host); + if (x != null && x instanceof HostEntry) + if (((HostEntry) x).isCanceled()) { + if (DebugHelper.DEBUG_REPOSITORY_CREDENTIALS) { + DebugHelper.debug("Credentials", "checkRememberCancel:PREVIOUSLY CANCELED", // //$NON-NLS-1$ //$NON-NLS-2$ + new Object[] {"host", host}); //$NON-NLS-1$ + } + + throw new LoginCanceledException(); + } + } + + } + + /** + * Increments the prompt count for host. If information is stale, the count is restarted + * at 1. + * @param host + */ + private static void incrementPromptCount(String host) { + Map r = getRemembered(); + if (r != null) { + HostEntry value = (HostEntry) r.get(host); + if (value == null) + r.put(host, value = new HostEntry(1)); + else { + if (value.isStale()) + value.reset(); + value.increment(); + } + } + } + + /** + * Returns prompt count for host, except if information is stale in which case 0 is returned. + * @param host + * @return number of time prompt has been performed for a host (or 0 if information is stale) + */ + private static int getPromptCount(String host) { + Map r = getRemembered(); + if (r != null) { + HostEntry value = (HostEntry) r.get(host); + if (value != null && !value.isStale()) + return value.getCount(); + } + return 0; + + } + + /** + * Clears the cached information about prompts for all login/password and + * canceled logins. + */ + public static synchronized void clearPromptCache() { + if (remembered == null) + return; + Map r = remembered; + if (r == null || r.isEmpty()) + return; + // reset entries rather than creating a new empty map since the entries + // are also used as locks + Iterator itor = r.entrySet().iterator(); + while (itor.hasNext()) + ((HostEntry) itor.next()).reset(); + } + + /** + * Clears the cached information for location about prompts for login/password and + * canceled logins. + * @param location the repository location + */ + public static synchronized void clearPromptCache(URI location) { + clearPromptCache(uriToHost(location)); + } + + /** + * Clears the cached information for host about prompts for login/password and + * canceled logins. + * @param host a host as returned from uriToHost for a location + */ + public static synchronized void clearPromptCache(String host) { + if (remembered == null) + return; + Map r = remembered; + if (r == null) + return; + HostEntry value = (HostEntry) r.get(host); + if (value != null) + value.reset(); + } + + private static synchronized Map getRemembered() { + if (remembered == null) + remembered = Collections.synchronizedMap(new HashMap()); + return remembered; + } + + private static class HostEntry { + long timestamp; + int count; + + public HostEntry(int count) { + this.count = count; + this.timestamp = System.currentTimeMillis(); + } + + public boolean isCanceled() { + return count == -1 && !isStale(); + } + + public boolean isStale() { + // a record is stale if older than 3 minutes + return System.currentTimeMillis() - timestamp > 1000 * 60 * 3; + } + + public int getCount() { + return count; + } + + public void increment() { + if (count != -1) + count++; + } + + public void reset() { + count = 0; + timestamp = System.currentTimeMillis(); + } + } } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileInfoReader.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileInfoReader.java index 20236bc6d..c84d4e2a6 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileInfoReader.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileInfoReader.java @@ -98,8 +98,9 @@ public class FileInfoReader extends Job implements IRemoteFileSystemListener { * @throws CoreException * @throws FileNotFoundException * @throws AuthenticationFailedException + * @throws JREHttpClientRequiredException */ - public IRemoteFile[] getRemoteFiles(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException { + public IRemoteFile[] getRemoteFiles(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { if (monitor != null) monitor.beginTask(location.toString(), 1); try { @@ -117,13 +118,13 @@ public class FileInfoReader extends Job implements IRemoteFileSystemListener { } - public IRemoteFile getRemoteFile(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException { + public IRemoteFile getRemoteFile(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { getRemoteFiles(location, monitor); return remoteFiles != null && remoteFiles.length > 0 ? remoteFiles[0] : null; } - public long getLastModified(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException { + public long getLastModified(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { IRemoteFile file = getRemoteFile(location, monitor); if (file == null) throw new FileNotFoundException(location.toString()); @@ -154,7 +155,7 @@ public class FileInfoReader extends Job implements IRemoteFileSystemListener { } } - protected void sendBrowseRequest(URI uri, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { + protected void sendBrowseRequest(URI uri, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { IContainer container; try { container = ContainerFactory.getDefault().createContainer(); @@ -202,10 +203,14 @@ public class FileInfoReader extends Job implements IRemoteFileSystemListener { * @throws CoreException * @throws FileNotFoundException * @throws AuthenticationFailedException + * @throws JREHttpClientRequiredException */ - private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException { + private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { // note that 'exception' could have been captured in a callback if (exception != null) { + // check if HTTP client needs to be changed + RepositoryStatusHelper.checkJREHttpClientRequired(exception); + // if this is a authentication failure - it is not meaningful to continue RepositoryStatusHelper.checkPermissionDenied(exception); diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java index 81846dd2f..5f3394b14 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java @@ -173,7 +173,7 @@ public final class FileReader extends FileTransferJob implements IFileTransferLi } } - public InputStream read(URI url, final IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { + public InputStream read(URI url, final IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { final PipedInputStream input = new PipedInputStream(); PipedOutputStream output; try { @@ -247,7 +247,7 @@ public final class FileReader extends FileTransferJob implements IFileTransferLi } public void readInto(URI uri, OutputStream anOutputStream, IProgressMonitor monitor) // - throws CoreException, FileNotFoundException, AuthenticationFailedException { + throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { readInto(uri, anOutputStream, -1, monitor); } @@ -256,7 +256,7 @@ public final class FileReader extends FileTransferJob implements IFileTransferLi } public void readInto(URI uri, OutputStream anOutputStream, long startPos, IProgressMonitor monitor) // - throws CoreException, FileNotFoundException, AuthenticationFailedException { + throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { if (monitor == null) monitor = new NullProgressMonitor(); try { @@ -284,7 +284,7 @@ public final class FileReader extends FileTransferJob implements IFileTransferLi } protected void sendRetrieveRequest(URI uri, OutputStream outputStream, DownloadRange range, boolean closeStreamOnFinish, // - IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { + IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { IRetrieveFileTransferFactory factory = Activator.getDefault().getRetrieveFileTransferFactory(); if (factory == null) { @@ -339,9 +339,12 @@ public final class FileReader extends FileTransferJob implements IFileTransferLi * @throws FileNotFoundException * @throws AuthenticationFailedException */ - private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException { + private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { // note that 'exception' could have been captured in a callback if (exception != null) { + // check if HTTP client needs to be changed + RepositoryStatusHelper.checkJREHttpClientRequired(exception); + // if this is an 'authentication failure' - it is not meaningful to continue RepositoryStatusHelper.checkPermissionDenied(exception); diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/JREHttpClientRequiredException.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/JREHttpClientRequiredException.java new file mode 100644 index 000000000..5d722aead --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/JREHttpClientRequiredException.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2009, Cloudsmith Inc. + * The code, documentation and other materials contained herein have been + * licensed under the Eclipse Public License - v 1.0 by the copyright holder + * listed above, as the Initial Contributor under such license. The text of + * such license is available at www.eclipse.org. + ******************************************************************************/ + +package org.eclipse.equinox.internal.p2.repository; + +import java.net.ProtocolException; + +/** + * Exception signaling that the JRE Http Client is required to handle the request. + */ +public class JREHttpClientRequiredException extends ProtocolException { + + private static final long serialVersionUID = 1L; + +} diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatusHelper.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatusHelper.java index 7f87da896..601a2da4d 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatusHelper.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatusHelper.java @@ -227,6 +227,22 @@ public abstract class RepositoryStatusHelper { } /** + * Check if the given exception represents that a switch to the JRE HTTP Client + * is required. ECF sets the HTTP status code 477 to indicate this. + * If the JRE HTTP client is required a JREHttpClientRequiredException is thrown. + */ + public static void checkJREHttpClientRequired(Throwable t) throws JREHttpClientRequiredException { + if (t instanceof IncomingFileTransferException) { + if (((IncomingFileTransferException) t).getErrorCode() == 477) + throw new JREHttpClientRequiredException(); + } else if (t instanceof BrowseFileTransferException) { + if (((BrowseFileTransferException) t).getErrorCode() == 477) + throw new JREHttpClientRequiredException(); + } + + } + + /** * Check if the given exception represents a permission failure (401 for HTTP), * and throw a AuthenticationFailedException if a permission failure was encountered. */ diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java index c8bef36b8..02d7027b3 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java @@ -58,6 +58,7 @@ public class RepositoryTransport extends Transport { public IStatus download(URI toDownload, OutputStream target, long startPos, IProgressMonitor monitor) { boolean promptUser = false; + boolean useJREHttp = false; AuthenticationInfo loginDetails = null; for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { FileReader reader = null; @@ -97,7 +98,12 @@ public class RepositoryTransport extends Transport { DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, // NLS.bind(Messages.UnableToRead_0_UserCanceled, toDownload), null); return statusOn(target, status, null); - + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } } } // reached maximum number of retries without success @@ -135,6 +141,7 @@ public class RepositoryTransport extends Transport { public InputStream stream(URI toDownload, IProgressMonitor monitor) throws FileNotFoundException, CoreException, AuthenticationFailedException { boolean promptUser = false; + boolean useJREHttp = false; AuthenticationInfo loginDetails = null; for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { FileReader reader = null; @@ -157,6 +164,12 @@ public class RepositoryTransport extends Transport { } catch (LoginCanceledException e) { // i.e. same behavior when user cancels as when failing n attempts. throw new AuthenticationFailedException(); + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } } } throw new AuthenticationFailedException(); @@ -195,6 +208,7 @@ public class RepositoryTransport extends Transport { */ public long getLastModified(URI toDownload, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { boolean promptUser = false; + boolean useJREHttp = false; AuthenticationInfo loginDetails = null; for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { try { @@ -215,7 +229,14 @@ public class RepositoryTransport extends Transport { } catch (LoginCanceledException e) { // same behavior as if user failed n attempts. throw new AuthenticationFailedException(); + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } } + } // reached maximum number of authentication retries without success throw new AuthenticationFailedException(); diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java new file mode 100644 index 000000000..f230f47c5 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.repository.helpers; + +import java.util.*; +import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.p2.repository.Activator; +import org.eclipse.osgi.service.debug.DebugOptions; + +public class DebugHelper { + public static final String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$ + + public static final boolean DEBUG_REPOSITORY_CREDENTIALS; + public static final boolean DEBUG_REPOSITORY_TRANSPORT; + + static { + DebugOptions options = (DebugOptions) ServiceHelper.getService(Activator.getContext(), DebugOptions.class.getName()); + if (options != null) { + DEBUG_REPOSITORY_CREDENTIALS = options.getBooleanOption(Activator.ID + "/credentials/debug", false); //$NON-NLS-1$ + DEBUG_REPOSITORY_TRANSPORT = options.getBooleanOption(Activator.ID + "/transport/debug", false); //$NON-NLS-1$ + } else { + DEBUG_REPOSITORY_CREDENTIALS = false; + DEBUG_REPOSITORY_TRANSPORT = false; + } + } + + public static void debug(String name, String message) { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); //$NON-NLS-1$ + buffer.append(Activator.ID + "-" + name); //$NON-NLS-1$ + buffer.append("] "); //$NON-NLS-1$ + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] " + LINE_SEPARATOR); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } + + public static void debug(String name, String message, Object[] keyValueArray) { + if (keyValueArray == null || keyValueArray.length == 0) + debug(name, message); + else { + Map params = new LinkedHashMap(keyValueArray.length / 2); + for (int i = 0; i < keyValueArray.length; i += 2) + params.put(keyValueArray[i], keyValueArray[i + 1]); + StringBuffer buffer = new StringBuffer(); + buffer.append(message); + buffer.append(formatMap(params, true, true)); + debug(name, buffer.toString()); + } + + StringBuffer buffer = new StringBuffer(); + buffer.append("["); //$NON-NLS-1$ + buffer.append(Activator.ID + "-" + name); //$NON-NLS-1$ + buffer.append("] "); //$NON-NLS-1$ + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] " + LINE_SEPARATOR); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } + + public static String formatArray(Object[] array, boolean toString, boolean newLines) { + if (array == null || array.length == 0) + return "[]"; //$NON-NLS-1$ + + StringBuffer buffer = new StringBuffer(); + buffer.append('['); + int i = 0; + for (;;) { + if (toString) + buffer.append(array[i].toString()); + else + buffer.append(array[i].getClass().getName()); + i++; + if (i == array.length) + break; + buffer.append(','); + if (newLines) + buffer.append(DebugHelper.LINE_SEPARATOR); + else + buffer.append(' '); + } + buffer.append(']'); + return buffer.toString(); + } + + public static String formatMap(Map map, boolean toString, boolean newLines) { + if (map == null || map.size() == 0) + return "[]"; //$NON-NLS-1$ + + StringBuffer buffer = new StringBuffer(); + buffer.append('['); + Iterator itor = map.entrySet().iterator(); + while (itor.hasNext()) { + Map.Entry e = (Map.Entry) itor.next(); + buffer.append(e.getKey()); + buffer.append('='); + if (toString) + buffer.append(e.getValue().toString()); + else + buffer.append(e.getValue().getClass().getName()); + + buffer.append(','); + if (newLines) { + buffer.append(DebugHelper.LINE_SEPARATOR); + buffer.append(" "); //$NON-NLS-1$ + } else + buffer.append(' '); + } + buffer.append(']'); + return buffer.toString(); + } + +} |