1 /******************************************************************************* 2 * Copyright (c) 2000, 2011 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.team.internal.ccvs.core.connection; 15 16 import java.io.IOException; 17 import java.util.*; 18 19 import org.eclipse.core.resources.ResourcesPlugin; 20 import org.eclipse.core.runtime.*; 21 import org.eclipse.core.runtime.Status; 22 import org.eclipse.core.runtime.jobs.ILock; 23 import org.eclipse.core.runtime.jobs.Job; 24 import org.eclipse.core.runtime.preferences.DefaultScope; 25 import org.eclipse.equinox.security.storage.*; 26 import org.eclipse.osgi.util.NLS; 27 import org.eclipse.team.core.TeamException; 28 import org.eclipse.team.internal.ccvs.core.*; 29 import org.eclipse.team.internal.ccvs.core.client.*; 30 import org.eclipse.team.internal.ccvs.core.resources.*; 31 import org.eclipse.team.internal.ccvs.core.util.KnownRepositories; 32 import org.osgi.service.prefs.BackingStoreException; 33 import org.osgi.service.prefs.Preferences; 34 35 /** 36 * This class manages a CVS repository location. 37 * 38 * It provides the mapping between connection method name and the 39 * plugged in ICunnectionMethod. 40 * 41 * It parses location strings into instances. 42 * 43 * It provides a method to open a connection to the server along 44 * with a method to validate that connections can be made. 45 * 46 * It manages its user info using the plugged in IUserAuthenticator 47 * (unless a username and password are provided as part of the creation 48 * string, in which case, no authenticator is used). 49 * 50 * Instances must be disposed of when no longer needed in order to 51 * notify the authenticator so cached properties can be cleared 52 */ 53 public class CVSRepositoryLocation extends PlatformObject implements ICVSRepositoryLocation, IUserInfo { 54 /** 55 * Top secure preferences node to cache CVS information 56 */ 57 static final private String cvsNameSegment = "/CVS/"; //$NON-NLS-1$ 58 59 /** 60 * Keys determining connection information for a given server 61 */ 62 static final private String PASSWORD_KEY = "password"; //$NON-NLS-1$ 63 static final private String USERNAME_KEY = "login"; //$NON-NLS-1$ 64 65 /** 66 * The name of the preferences node in the CVS preferences that contains 67 * the known repositories as its children. 68 */ 69 public static final String PREF_REPOSITORIES_NODE = "repositories"; //$NON-NLS-1$ 70 71 /* 72 * The name of the node in the default scope that has the default settings 73 * for a repository. 74 */ 75 private static final String DEFAULT_REPOSITORY_SETTINGS_NODE = "default_repository_settings"; //$NON-NLS-1$ 76 77 // Preference keys used to persist the state of the location 78 public static final String PREF_LOCATION = "location"; //$NON-NLS-1$ 79 public static final String PREF_SERVER_ENCODING = "encoding"; //$NON-NLS-1$ 80 81 // server platform constants 82 public static final int UNDETERMINED_PLATFORM = 0; 83 public static final int CVS_SERVER = 1; 84 public static final int CVSNT_SERVER = 2; 85 public static final int UNSUPPORTED_SERVER = 3; 86 public static final int UNKNOWN_SERVER = 4; 87 88 // static variables for extension points 89 private static IUserAuthenticator authenticator; 90 private static IConnectionMethod[] pluggedInConnectionMethods = null; 91 92 // Locks for ensuring that authentication to a host is serialized 93 // so that invalid passwords do not result in account lockout 94 private static Map<String, ILock> hostLocks = new HashMap<>(); 95 96 private IConnectionMethod method; 97 private String user; 98 private String password; 99 private String host; 100 private int port; 101 private String root; 102 private boolean userFixed; 103 private boolean passwordFixed; 104 private boolean allowCaching; 105 106 private int serverPlatform = UNDETERMINED_PLATFORM; 107 108 public static final char COLON = ':'; 109 public static final char SEMICOLON = ';'; 110 public static final char HOST_SEPARATOR = '@'; 111 public static final char PORT_SEPARATOR = '#'; 112 public static final boolean STANDALONE_MODE = (System.getProperty("eclipse.cvs.standalone")==null) ? //$NON-NLS-1$ 113 false :(Boolean.valueOf(System.getProperty("eclipse.cvs.standalone")).booleanValue()); //$NON-NLS-1$ 114 115 // command to start remote cvs in server mode 116 private static final String INVOKE_SVR_CMD = "server"; //$NON-NLS-1$ 117 118 // fields needed for caching the password 119 public static final String INFO_PASSWORD = "org.eclipse.team.cvs.core.password";//$NON-NLS-1$ 120 public static final String INFO_USERNAME = "org.eclipse.team.cvs.core.username";//$NON-NLS-1$ 121 public static final String AUTH_SCHEME = "";//$NON-NLS-1$ 122 123 /* 124 * Fields used to create the EXT command invocation 125 */ 126 public static final String USER_VARIABLE = "{user}"; //$NON-NLS-1$ 127 public static final String PASSWORD_VARIABLE = "{password}"; //$NON-NLS-1$ 128 public static final String HOST_VARIABLE = "{host}"; //$NON-NLS-1$ 129 public static final String PORT_VARIABLE = "{port}"; //$NON-NLS-1$ 130 131 /* 132 * Field that indicates which connection method is to be used for 133 * locations that use the EXT connection method. 134 */ 135 private static String extProxy; 136 137 /* 138 * Field that indicates that the last connection attempt made to 139 * this repository location failed due to an authentication failure. 140 * When this is set, subsequent attempts should prompt before attempting to connect 141 */ 142 private boolean previousAuthenticationFailed = false; 143 144 /** 145 * Return the preferences node whose child nodes are the know repositories 146 * @return a preferences node 147 */ getParentPreferences()148 public static Preferences getParentPreferences() { 149 return CVSProviderPlugin.getPlugin().getInstancePreferences().node(PREF_REPOSITORIES_NODE); 150 } 151 152 /** 153 * Return a preferences node that contains suitable defaults for a 154 * repository location. 155 * @return a preferences node 156 */ getDefaultPreferences()157 public static Preferences getDefaultPreferences() { 158 Preferences defaults = DefaultScope.INSTANCE.getNode(CVSProviderPlugin.ID).node(DEFAULT_REPOSITORY_SETTINGS_NODE); 159 defaults.put(PREF_SERVER_ENCODING, getDefaultEncoding()); 160 return defaults; 161 } 162 getDefaultEncoding()163 private static String getDefaultEncoding() { 164 return System.getProperty("file.encoding", "UTF-8"); //$NON-NLS-1$ //$NON-NLS-2$ 165 } 166 167 /** 168 * Set the proxy connection method that is to be used when a 169 * repository location has the ext connection method. This is 170 * usefull with the extssh connection method as it can be used to 171 * keep the sandbox compatible with the command line client. 172 * @param string 173 */ setExtConnectionMethodProxy(String string)174 public static void setExtConnectionMethodProxy(String string) { 175 extProxy = string; 176 } 177 178 /** 179 * Create a repository location instance from the given properties. 180 * The supported properties are: 181 * 182 * connection The connection method to be used 183 * user The username for the connection (optional) 184 * password The password used for the connection (optional) 185 * host The host where the repository resides 186 * port The port to connect to (optional) 187 * root The server directory where the repository is located 188 * encoding The file system encoding of the server 189 */ fromProperties(Properties configuration)190 public static CVSRepositoryLocation fromProperties(Properties configuration) throws CVSException { 191 // We build a string to allow validation of the components that are provided to us 192 String connection = configuration.getProperty("connection");//$NON-NLS-1$ 193 if (connection == null) 194 connection = "pserver";//$NON-NLS-1$ 195 IConnectionMethod method = getPluggedInConnectionMethod(connection); 196 if (method == null) 197 throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, NLS.bind(CVSMessages.CVSRepositoryLocation_methods, (new Object[] {getPluggedInConnectionMethodNames()})), null));// 198 String user = configuration.getProperty("user");//$NON-NLS-1$ 199 if (user.length() == 0) 200 user = null; 201 String password = configuration.getProperty("password");//$NON-NLS-1$ 202 if (user == null) 203 password = null; 204 String host = configuration.getProperty("host");//$NON-NLS-1$ 205 if (host == null) 206 throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, CVSMessages.CVSRepositoryLocation_hostRequired, null));// 207 String portString = configuration.getProperty("port");//$NON-NLS-1$ 208 int port; 209 if (portString == null) 210 port = ICVSRepositoryLocation.USE_DEFAULT_PORT; 211 else 212 port = Integer.parseInt(portString); 213 String root = configuration.getProperty("root");//$NON-NLS-1$ 214 if (root == null) 215 throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, CVSMessages.CVSRepositoryLocation_rootRequired, null));// 216 217 String encoding = configuration.getProperty("encoding"); //$NON-NLS-1$ 218 219 return new CVSRepositoryLocation(method, user, password, host, port, root, encoding, user != null, false); 220 } 221 222 /** 223 * Parse a location string and return a CVSRepositoryLocation. 224 * 225 * On failure, the status of the exception will be a MultiStatus 226 * that includes the original parsing error and a general status 227 * displaying the passed location and proper form. This form is 228 * better for logging, etc. 229 */ fromString(String location)230 public static CVSRepositoryLocation fromString(String location) throws CVSException { 231 try { 232 return fromString(location, false); 233 } catch (CVSException e) { 234 // Parsing failed. Include a status that 235 // shows the passed location and the proper form 236 MultiStatus error = new MultiStatus(CVSProviderPlugin.ID, IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_invalidFormat, (new Object[] {location})), null);// 237 error.merge(new CVSStatus(IStatus.ERROR, CVSMessages.CVSRepositoryLocation_locationForm));// 238 error.merge(e.getStatus()); 239 throw new CVSException(error); 240 } 241 } 242 243 /** 244 * Parse a location string and return a CVSRepositoryLocation. 245 * 246 * The valid format (from the cederqvist) is: 247 * 248 * :method:[[user][:password]@]hostname[:[port]]/path/to/repository 249 * 250 * However, this does not work with CVS on NT so we use the format 251 * 252 * :method:[user[:password]@]hostname[#port]:/path/to/repository 253 * 254 * Some differences to note: 255 * The : after the host/port is not optional because of NT naming including device 256 * e.g. :pserver:username:password@hostname#port:D:\cvsroot 257 * 258 * Also parse alternative format from WinCVS, which stores connection 259 * parameters such as username and hostname in method options: 260 * 261 * :method[;option=arg...]:other_connection_data 262 * 263 * e.g. :pserver;username=anonymous;hostname=localhost:/path/to/repository 264 * 265 * If validateOnly is true, this method will always throw an exception. 266 * The status of the exception indicates success or failure. The status 267 * of the exception contains a specific message suitable for displaying 268 * to a user who has knowledge of the provided location string. 269 * @see CVSRepositoryLocation#fromString(String) 270 */ fromString(String location, boolean validateOnly)271 public static CVSRepositoryLocation fromString(String location, boolean validateOnly) throws CVSException { 272 String errorMessage = null; 273 try { 274 // Get the connection method 275 errorMessage = CVSMessages.CVSRepositoryLocation_parsingMethod; 276 int start = location.indexOf(COLON); 277 String methodName; 278 int end; 279 // For parsing alternative location format 280 int optionStart = location.indexOf(SEMICOLON); 281 HashMap<String, String> hmOptions = new HashMap<>(); 282 283 if (start == 0) { 284 end = location.indexOf(COLON, start + 1); 285 286 // Check for alternative location syntax 287 if (optionStart != -1) { 288 // errorMessage = CVSMessages.CVSRepositoryLocation_parsingMethodOptions; 289 methodName = location.substring(start + 1, optionStart); 290 // Save options in hash table 291 StringTokenizer stOpt = new StringTokenizer( 292 location.substring(optionStart+1, end), 293 "=;" //$NON-NLS-1$ 294 ); 295 while (stOpt.hasMoreTokens()) { 296 hmOptions.put(stOpt.nextToken(), stOpt.nextToken()); 297 } 298 start = end + 1; 299 } else { 300 methodName = location.substring(start + 1, end); 301 start = end + 1; 302 } 303 } else { 304 // this could be an alternate format for ext: username:password@host:path 305 methodName = "ext"; //$NON-NLS-1$ 306 start = 0; 307 } 308 309 IConnectionMethod method = getPluggedInConnectionMethod(methodName); 310 if (method == null) 311 throw new CVSException(new CVSStatus(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_methods, (new Object[] {getPluggedInConnectionMethodNames()}))));// 312 313 // Get the user name and password (if provided) 314 errorMessage = CVSMessages.CVSRepositoryLocation_parsingUser; 315 //Since there is a @ sign in the user name so use lastIndexOf to get to the host separator @ 316 end = location.lastIndexOf(HOST_SEPARATOR, location.length()); 317 String user = null; 318 String password = null; 319 // if end is -1 then there is no host separator meaning that the username is not present 320 // or set in options of alternative-style location string 321 if (end != -1) { 322 // Get the optional user and password 323 user = location.substring(start, end); 324 // Separate the user and password (if there is a password) 325 start = user.indexOf(COLON); 326 if (start != -1) { 327 errorMessage = CVSMessages.CVSRepositoryLocation_parsingPassword; 328 password = user.substring(start+1); 329 user = user.substring(0, start); 330 } 331 // Set start to point after the host separator 332 start = end + 1; 333 } else if (optionStart != -1) { 334 // alternative location string data 335 // errorMessage = CVSMessages.CVSRepositoryLocation_parsingOptionsUsername; 336 if (hmOptions.containsKey("username")) user = hmOptions.get("username").toString(); //$NON-NLS-1$ //$NON-NLS-2$ 337 // errorMessage = CVSMessages.CVSRepositoryLocation_parsingOptionsPassword; 338 if (hmOptions.containsKey("password")) password = hmOptions.get("password").toString(); //$NON-NLS-1$ //$NON-NLS-2$ 339 } 340 341 // Get the host (and port) 342 errorMessage = CVSMessages.CVSRepositoryLocation_parsingHost; 343 end= location.indexOf(COLON, start); 344 int hostEnd = end; 345 if (end == -1) { 346 // The last colon is optional so look for the slash that starts the path 347 end = location.indexOf('/', start); 348 hostEnd = end; 349 // Decrement the end since the slash is part of the path 350 if (end != -1) end--; 351 } 352 String host = (hmOptions.containsKey("hostname")) ? hmOptions.get("hostname").toString() : location.substring(start, hostEnd); //$NON-NLS-1$ //$NON-NLS-2$ 353 int port = USE_DEFAULT_PORT; 354 boolean havePort = false; 355 if (hmOptions.containsKey("port")) { //$NON-NLS-1$ 356 port = Integer.parseInt(hmOptions.get("port").toString()); //$NON-NLS-1$ 357 havePort = true; 358 } 359 // Separate the port and host if there is a port 360 start = host.indexOf(PORT_SEPARATOR); 361 if (start != -1) { 362 try { 363 // Initially, we used a # between the host and port 364 errorMessage = CVSMessages.CVSRepositoryLocation_parsingPort; 365 port = Integer.parseInt(host.substring(start+1)); 366 host = host.substring(0, start); 367 havePort = true; 368 } catch (NumberFormatException e) { 369 // Ignore this as the #1234 port could be part of a proxy host string 370 } 371 } 372 if (!havePort) { 373 // In the correct CVS format, the port follows the COLON 374 errorMessage = CVSMessages.CVSRepositoryLocation_parsingPort; 375 int index = end; 376 char c = location.charAt(++index); 377 String portString = new String(); 378 while (Character.isDigit(c)) { 379 portString += c; 380 c = location.charAt(++index); 381 } 382 if (portString.length() > 0) { 383 end = index - 1; 384 port = Integer.parseInt(portString); 385 } 386 } 387 388 // Get the repository path (translating backslashes to slashes) 389 errorMessage = CVSMessages.CVSRepositoryLocation_parsingRoot; 390 start = end + 1; 391 String root = location.substring(start); 392 393 if (validateOnly) 394 throw new CVSException(new CVSStatus(IStatus.OK, CVSMessages.ok));// 395 return new CVSRepositoryLocation(method, user, password, host, port, root, null /* encoding */, (user != null), (password != null)); 396 } catch (IndexOutOfBoundsException e) { 397 // We'll get here if anything funny happened while extracting substrings 398 IStatus status = new CVSStatus(IStatus.ERROR, errorMessage); 399 throw new CVSException(status); 400 } catch (NumberFormatException e) { 401 IStatus status = new CVSStatus(IStatus.ERROR, errorMessage); 402 // We'll get here if we couldn't parse a number 403 throw new CVSException(status); 404 } 405 } 406 407 /** 408 * Get the plugged-in user authenticator if there is one. 409 * @return the plugged-in user authenticator or <code>null</code> 410 */ getAuthenticator()411 public static IUserAuthenticator getAuthenticator() { 412 if (authenticator == null) { 413 authenticator = getPluggedInAuthenticator(); 414 } 415 return authenticator; 416 } 417 418 /** 419 * Return the sorted array of plugged-in connection methods. 420 * @return the sorted array of plugged-in connection methods 421 */ getPluggedInConnectionMethods()422 public static IConnectionMethod[] getPluggedInConnectionMethods() { 423 if(pluggedInConnectionMethods==null) { 424 List<Object> connectionMethods = new ArrayList<>(); 425 426 if (STANDALONE_MODE) { 427 connectionMethods.add(new PServerConnectionMethod()); 428 } else { 429 IExtension[] extensions = Platform.getExtensionRegistry().getExtensionPoint(CVSProviderPlugin.ID, CVSProviderPlugin.PT_CONNECTIONMETHODS).getExtensions(); 430 for (IExtension extension : extensions) { 431 IConfigurationElement[] configs = extension.getConfigurationElements(); 432 if (configs.length == 0) { 433 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Connection method {0} is missing required fields", new Object[] {extension.getUniqueIdentifier()}), null);//$NON-NLS-1$ 434 continue; 435 } 436 try { 437 IConfigurationElement config = configs[0]; 438 connectionMethods.add(config.createExecutableExtension("run"));//$NON-NLS-1$ 439 } catch (CoreException ex) { 440 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Could not instantiate connection method for {0}", new Object[] {extension.getUniqueIdentifier()}), ex);//$NON-NLS-1$ 441 } 442 } 443 } 444 IConnectionMethod[] methods = connectionMethods.toArray(new IConnectionMethod[0]); 445 Arrays.sort(methods, (cm1, cm2) -> cm1.getName().compareTo(cm2.getName())); 446 pluggedInConnectionMethods = methods; 447 } 448 return pluggedInConnectionMethods; 449 } 450 451 /* 452 * Return the connection method registered for the given name 453 * or <code>null</code> if none is registered with the given name. 454 */ getPluggedInConnectionMethod(String methodName)455 private static IConnectionMethod getPluggedInConnectionMethod(String methodName) { 456 Assert.isNotNull(methodName); 457 IConnectionMethod[] methods = getPluggedInConnectionMethods(); 458 for (IConnectionMethod m : methods) { 459 if (methodName.equals(m.getName())) { 460 return m; 461 } 462 } 463 return null; 464 } 465 466 /* 467 * Return a string containing a list of all connection methods 468 * that is suitable for inclusion in an error message. 469 */ getPluggedInConnectionMethodNames()470 private static String getPluggedInConnectionMethodNames() { 471 IConnectionMethod[] methods = getPluggedInConnectionMethods(); 472 StringBuilder methodNames = new StringBuilder(); 473 for(int i=0; i<methods.length; i++) { 474 String name = methods[i].getName(); 475 if (i>0) 476 methodNames.append(", ");//$NON-NLS-1$ 477 methodNames.append(name); 478 } 479 return methodNames.toString(); 480 } 481 482 /* 483 * Get the pluged-in authenticator from the plugin manifest. 484 */ getPluggedInAuthenticator()485 private static IUserAuthenticator getPluggedInAuthenticator() { 486 IExtension[] extensions = Platform.getExtensionRegistry().getExtensionPoint(CVSProviderPlugin.ID, CVSProviderPlugin.PT_AUTHENTICATOR).getExtensions(); 487 if (extensions.length == 0) 488 return null; 489 IExtension extension = extensions[0]; 490 IConfigurationElement[] configs = extension.getConfigurationElements(); 491 if (configs.length == 0) { 492 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("User autheticator {0} is missing required fields", (new Object[] {extension.getUniqueIdentifier()})), null);//$NON-NLS-1$ 493 return null; 494 } 495 try { 496 IConfigurationElement config = configs[0]; 497 return (IUserAuthenticator) config.createExecutableExtension("run");//$NON-NLS-1$ 498 } catch (CoreException ex) { 499 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Unable to instantiate user authenticator {0}", (new Object[] {extension.getUniqueIdentifier()})), ex);//$NON-NLS-1$ 500 return null; 501 } 502 } 503 504 /* 505 * Create a CVSRepositoryLocation from its composite parts. 506 */ CVSRepositoryLocation(IConnectionMethod method, String user, String password, String host, int port, String root, String encoding, boolean userFixed, boolean passwordFixed)507 private CVSRepositoryLocation(IConnectionMethod method, String user, String password, String host, int port, String root, String encoding, boolean userFixed, boolean passwordFixed) { 508 this.method = method; 509 this.user = user; 510 this.password = password; 511 this.host = host; 512 this.port = port; 513 this.root = root; 514 // The username can be fixed only if one is provided 515 if (userFixed && (user != null)) 516 this.userFixed = true; 517 // The password can only be fixed if the username is and a password is provided 518 if (userFixed && passwordFixed && (password != null)) 519 this.passwordFixed = true; 520 if (encoding != null) { 521 setEncoding(encoding); 522 } 523 } 524 525 /* 526 * Create the connection to the remote server. 527 * If anything fails, an exception will be thrown and must 528 * be handled by the caller. 529 */ createConnection(String password, IProgressMonitor monitor)530 private Connection createConnection(String password, IProgressMonitor monitor) throws CVSException { 531 IConnectionMethod methodToUse = method; 532 if (method.getName().equals("ext") && extProxy != null && !extProxy.equals(method.getName())) { //$NON-NLS-1$ 533 methodToUse = getPluggedInConnectionMethod(extProxy); 534 } 535 Connection connection = new Connection(this, methodToUse.createConnection(this, password)); 536 connection.open(monitor); 537 return connection; 538 } 539 540 /* 541 * Dispose of the receiver by clearing any cached authorization information. 542 * This method should only be invoked when the corresponding adapter is shut 543 * down or a connection is being validated. 544 */ dispose()545 public void dispose() { 546 removeNode(); 547 try { 548 if (hasPreferences()) { 549 internalGetPreferences().removeNode(); 550 getParentPreferences().flush(); 551 } 552 } catch (BackingStoreException e) { 553 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_73, new String[] { getLocation(true) }), e); 554 } 555 } 556 557 /* 558 * Clear and flush the keyring entry associated with the receiver 559 */ removeNode()560 private void removeNode() { 561 ISecurePreferences node = getCVSNode(); 562 if (node == null) 563 return; 564 try { 565 node.clear(); 566 node.flush(); // save immediately 567 } catch (IllegalStateException e) { 568 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 569 } catch (IOException e) { 570 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 571 } 572 } 573 574 /* 575 * @see ICVSRepositoryLocation#getHost() 576 */ getHost()577 public String getHost() { 578 return host; 579 } 580 581 /* 582 * @see IRepositoryLocation#getLocation() 583 * 584 * The username is included if it is fixed. 585 * The password is never included even if it is fixed. 586 * The port is included if it is not the default port. 587 */ getLocation()588 public String getLocation() { 589 return getLocation(false); 590 } 591 getLocation(boolean forDisplay)592 public String getLocation(boolean forDisplay) { 593 return COLON + method.getName() + COLON + 594 (userFixed?(user + 595 ((passwordFixed && !forDisplay)?(COLON + password):"")//$NON-NLS-1$ 596 + HOST_SEPARATOR):"") +//$NON-NLS-1$ 597 host + COLON + 598 ((port == USE_DEFAULT_PORT)?"":(Integer.valueOf(port).toString())) + //$NON-NLS-1$ 599 root; 600 } 601 602 @Override getMethod()603 public IConnectionMethod getMethod() { 604 return method; 605 } 606 607 @Override getPort()608 public int getPort() { 609 return port; 610 } 611 612 @Override getEncoding()613 public String getEncoding() { 614 if (hasPreferences()) { 615 return internalGetPreferences().get(PREF_SERVER_ENCODING, getDefaultEncoding()); 616 } else { 617 return getDefaultEncoding(); 618 } 619 } 620 621 @Override setEncoding(String encoding)622 public void setEncoding(String encoding) { 623 if (encoding == null || encoding == getDefaultEncoding()) { 624 if (hasPreferences()) { 625 internalGetPreferences().remove(PREF_SERVER_ENCODING); 626 } 627 } else { 628 ensurePreferencesStored(); 629 internalGetPreferences().put(PREF_SERVER_ENCODING, encoding); 630 flushPreferences(); 631 } 632 } 633 634 @Override members(CVSTag tag, boolean modules, IProgressMonitor progress)635 public ICVSRemoteResource[] members(CVSTag tag, boolean modules, IProgressMonitor progress) throws CVSException { 636 try { 637 if (modules) { 638 return RemoteModule.getRemoteModules(this, tag, progress); 639 } else { 640 RemoteFolder root = new RemoteFolder(null, this, ICVSRemoteFolder.REPOSITORY_ROOT_FOLDER_NAME, tag); 641 ICVSRemoteResource[] resources = root.members(progress); 642 // There is the off chance that there is a file in the root of the repository. 643 // This is not supported by cvs so we need to make sure there are no files 644 List<ICVSRemoteResource> folders = new ArrayList<>(resources.length); 645 for (ICVSRemoteResource remoteResource : resources) { 646 if (remoteResource.isContainer()) { 647 folders.add(remoteResource); 648 } 649 } 650 return folders.toArray(new ICVSRemoteResource[folders.size()]); 651 } 652 } catch (CVSException e){ 653 // keep current CVSException 654 throw e; 655 } catch(TeamException e1) { 656 throw new CVSException(e1.getStatus()); 657 } 658 } 659 660 @Override getRemoteFolder(String remotePath, CVSTag tag)661 public ICVSRemoteFolder getRemoteFolder(String remotePath, CVSTag tag) { 662 return new RemoteFolder(null, this, remotePath, tag); 663 } 664 665 @Override getRemoteFile(String remotePath, CVSTag tag)666 public ICVSRemoteFile getRemoteFile(String remotePath, CVSTag tag) { 667 IPath path = new Path(null, remotePath); 668 RemoteFolderTree remoteFolder = new RemoteFolderTree(null, this, path.removeLastSegments(1).toString(), tag); 669 RemoteFile remoteFile = new RemoteFile(remoteFolder, Update.STATE_ADDED_LOCAL, path.lastSegment(), null, null, tag); 670 remoteFolder.setChildren(new ICVSRemoteResource[] { remoteFile }); 671 return remoteFile; 672 } 673 674 @Override getRootDirectory()675 public String getRootDirectory() { 676 return root; 677 } 678 679 /* 680 * @see ICVSRepositoryLocation#getTimeout() 681 * 682 * For the time being, the timeout value is a system wide value 683 * associated with the CVSPlugin singleton. 684 */ getTimeout()685 public int getTimeout() { 686 return CVSProviderPlugin.getPlugin().getTimeout(); 687 } 688 689 @Override getUserInfo(boolean makeUsernameMutable)690 public IUserInfo getUserInfo(boolean makeUsernameMutable) { 691 return new UserInfo(getUsername(), password, makeUsernameMutable ? true : isUsernameMutable()); 692 } 693 694 @Override getUsername()695 public String getUsername() { 696 // If the username is mutable, get it from the cache if it's there 697 if (user == null && isUsernameMutable()) { 698 retrieveUsername(); 699 } 700 return user == null ? "" : user; //$NON-NLS-1$ 701 } 702 703 @Override isUsernameMutable()704 public boolean isUsernameMutable() { 705 return !userFixed; 706 } 707 708 /* 709 * Open a connection to the repository represented by the receiver. 710 * If the username or password are not fixed, openConnection will 711 * use the plugged-in authenticator to prompt for the username and/or 712 * password if one has not previously been provided or if the previously 713 * supplied username and password are invalid. 714 * 715 * This method is synchronized to ensure that authentication with the 716 * remote server is serialized. This is needed to avoid the situation where 717 * multiple failed authentications occur and result in the remote account 718 * being locked. The CVSProviderPlugin enforces that there is one instance 719 * of a CVSRepositoryLocation per remote location thus this method is called 720 * for any connection made to this remote location. 721 */ openConnection(IProgressMonitor monitor)722 public Connection openConnection(IProgressMonitor monitor) throws CVSException { 723 // Get the lock for the host to ensure that we are not connecting to the same host concurrently. 724 Policy.checkCanceled(monitor); 725 ILock hostLock; 726 synchronized(hostLocks) { 727 hostLock = hostLocks.get(getHost()); 728 if (hostLock == null) { 729 hostLock = Job.getJobManager().newLock(); 730 hostLocks.put(getHost(), hostLock); 731 } 732 } 733 try { 734 boolean acquired = false; 735 int count = 0; 736 int timeout = CVSProviderPlugin.getPlugin().getTimeout(); 737 while (!acquired) { 738 try { 739 acquired = hostLock.acquire(1000); 740 } catch (InterruptedException e) { 741 // Ignore 742 } 743 if (timeout > 0 && count > timeout) { 744 throw new CVSCommunicationException(NLS.bind(CVSMessages.CVSRepositoryLocation_72, getHost())); 745 } 746 count++; 747 Policy.checkCanceled(monitor); 748 } 749 // Allow two ticks in case of a retry 750 monitor.beginTask(NLS.bind(CVSMessages.CVSRepositoryLocation_openingConnection, new String[] { getHost() }), 2); 751 ensureLocationCached(); 752 boolean cacheNeedsUpdate = false; 753 // If the previous connection failed, prompt before attempting to connect 754 if (previousAuthenticationFailed) { 755 promptForUserInfo(null); 756 // The authentication information has been change so update the cache 757 cacheNeedsUpdate = true; 758 } 759 while (true) { 760 try { 761 // The following will throw an exception if authentication fails 762 String password = this.password; 763 if (password == null) { 764 // If the instance has no password, obtain it from the cache 765 password = retrievePassword(); 766 } 767 if (user == null) { 768 // This is possible if the cache was cleared somehow for a location with a mutable username 769 throw new CVSAuthenticationException(CVSMessages.CVSRepositoryLocation_usernameRequired, CVSAuthenticationException.RETRY, this, null); 770 } 771 //if (password == null) 772 // password = "";//$NON-NLS-1$ 773 Connection connection = createConnection(password, monitor); 774 if (cacheNeedsUpdate) 775 updateCachedLocation(); 776 previousAuthenticationFailed = false; 777 return connection; 778 } catch (CVSAuthenticationException ex) { 779 previousAuthenticationFailed = true; 780 if (ex.getRetryStatus() == CVSAuthenticationException.RETRY) { 781 String message = ex.getMessage(); 782 promptForUserInfo(message); 783 // The authentication information has been change so update the cache 784 cacheNeedsUpdate = true; 785 } else { 786 throw ex; 787 } 788 } 789 } 790 } finally { 791 hostLock.release(); 792 monitor.done(); 793 } 794 } 795 796 /* 797 * Prompt for the user authentication information (i.e. user name and password). 798 */ promptForUserInfo(String message)799 private void promptForUserInfo(String message) throws CVSException { 800 IUserAuthenticator authenticator = getAuthenticator(); 801 if (authenticator == null) { 802 throw new CVSAuthenticationException(CVSMessages.CVSRepositoryLocation_noAuthenticator, CVSAuthenticationException.NO_RETRY,this);// 803 } 804 authenticator.promptForUserInfo(this, this, message); 805 } 806 807 /* 808 * Ensure that this location is in the known repositories list 809 * and that the authentication information matches what is in the 810 * cache, if this instance is not the instance in the cache. 811 */ ensureLocationCached()812 private void ensureLocationCached() { 813 String location = getLocation(); 814 KnownRepositories repositories = KnownRepositories.getInstance(); 815 if (repositories.isKnownRepository(location)) { 816 try { 817 // The repository is already known. 818 // Ensure that the authentication information of this 819 // location matches that of the known location 820 setAuthenticationInformation((CVSRepositoryLocation)repositories.getRepository(location)); 821 } catch (CVSException e) { 822 // Log the exception and continue 823 CVSProviderPlugin.log(e); 824 } 825 } else { 826 // The repository is not known so record it so any authentication 827 // information the user may provide is remembered 828 repositories.addRepository(this, true /* broadcast */); 829 } 830 } 831 832 /* 833 * Set the authentication information of this instance such that it matches the 834 * provided instances. 835 */ setAuthenticationInformation(CVSRepositoryLocation other)836 private void setAuthenticationInformation(CVSRepositoryLocation other) { 837 if (other != this) { 838 // The instances differ so copy from the other location to this one 839 if (other.getUserInfoCached()) { 840 // The user info is cached for the other instance 841 // so null all the values in this instance so the 842 // information is obtained from the cache 843 this.allowCaching = true; 844 if (!userFixed) this.user = null; 845 if (!passwordFixed) this.password = null; 846 } else { 847 // The user info is not cached for the other instance so 848 // copy the authentication information into this instance 849 setAllowCaching(false); /* this will clear any cached values */ 850 // Only copy the username and password if they are not fixed. 851 // (If they are fixed, they would be included in the location 852 // identifier and therefore must already match) 853 if (!other.userFixed) 854 this.user = other.user; 855 if (!other.passwordFixed) 856 this.password = other.password; 857 } 858 } 859 } 860 861 /* 862 * The connection was successfully made. Update the cached 863 * repository location if it is a different instance than 864 * this location. 865 */ updateCachedLocation()866 private void updateCachedLocation() { 867 try { 868 CVSRepositoryLocation known = (CVSRepositoryLocation)KnownRepositories.getInstance().getRepository(getLocation()); 869 known.setAuthenticationInformation(this); 870 } catch (CVSException e) { 871 // Log the exception and continue 872 CVSProviderPlugin.log(e); 873 } 874 } 875 876 /* 877 * Implementation of inherited toString() 878 */ toString()879 public String toString() { 880 return getLocation(true); 881 } 882 equals(Object o)883 public boolean equals(Object o) { 884 if (this == o) return true; 885 if (!(o instanceof CVSRepositoryLocation)) return false; 886 return getLocation().equals(((CVSRepositoryLocation)o).getLocation()); 887 } hashCode()888 public int hashCode() { 889 return getLocation().hashCode(); 890 } 891 892 /* 893 * Set the username of the receiver if the username is mutable. Return the 894 * username from the keyring if available. 895 */ retrieveUsername()896 private String retrieveUsername() { 897 ISecurePreferences node = getCVSNode(); 898 if (node == null) 899 return null; 900 try { 901 String username = node.get(USERNAME_KEY, null); 902 if (username != null && isUsernameMutable()) 903 setUsername(username); 904 return username; 905 } catch (StorageException e) { // most likely invalid keyring password or corrupted data 906 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 907 } 908 return null; 909 } 910 911 /* 912 * Return the cached password from the keyring. 913 * Also, set the username of the receiver if the username is mutable 914 */ retrievePassword()915 private String retrievePassword() { 916 ISecurePreferences node = getCVSNode(); 917 if (node == null) 918 return null; 919 try { 920 retrieveUsername(); 921 return node.get(PASSWORD_KEY, null); 922 } catch (StorageException e) { // most likely invalid keyring password or corrupted data 923 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 924 } 925 return null; 926 } 927 @Override setPassword(String password)928 public void setPassword(String password) { 929 if (passwordFixed) 930 throw new UnsupportedOperationException(); 931 // We set the password here but it will be cleared 932 // if the user info is cached using updateCache() 933 this.password = password; 934 // The password has been changed, reset the flag, so we won't 935 // prompt before attempting to connect 936 previousAuthenticationFailed = false; 937 } 938 939 @Override setUsername(String user)940 public void setUsername(String user) { 941 if (userFixed) 942 throw new UnsupportedOperationException(); 943 this.user = user; 944 } 945 setUserMuteable(boolean muteable)946 public void setUserMuteable(boolean muteable) { 947 userFixed = !muteable; 948 } 949 setAllowCaching(boolean value)950 public void setAllowCaching(boolean value) { 951 allowCaching = value; 952 if (allowCaching) { 953 updateCache(); 954 } else { 955 if (password == null) 956 password = retrievePassword(); 957 removeNode(); 958 } 959 } 960 updateCache()961 public void updateCache() { 962 // Nothing to cache if the password is fixed 963 if (passwordFixed || ! allowCaching) return; 964 // Nothing to cache if the password is null and the user is fixed 965 if (password == null && userFixed) return; 966 if (updateCache(user, password)) { 967 // If the cache was updated, null the password field 968 // so we will obtain the password from the cache when needed 969 password = null; 970 } 971 ensurePreferencesStored(); 972 } 973 974 /* 975 * Cache the user info in the keyring. Return true if the operation 976 * succeeded and false otherwise. If an error occurs, it will be logged. 977 */ updateCache(String username, String password)978 private boolean updateCache(String username, String password) { 979 ISecurePreferences node = getCVSNode(); 980 if (node == null) 981 return false; 982 try { 983 node.put(USERNAME_KEY, username, false); 984 node.put(PASSWORD_KEY, password, true); 985 node.flush(); 986 } catch (StorageException e) { 987 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 988 return false; 989 } catch (IOException e) { 990 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 991 return false; 992 } 993 return true; 994 } 995 996 /* 997 * Validate that the receiver contains valid information for 998 * making a connection. If the receiver contains valid 999 * information, the method returns. Otherwise, an exception 1000 * indicating the problem is throw. 1001 */ validateConnection(IProgressMonitor monitor)1002 public void validateConnection(IProgressMonitor monitor) throws CVSException { 1003 try { 1004 monitor = Policy.monitorFor(monitor); 1005 monitor.beginTask(null, 100); 1006 ICVSFolder root = CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()); 1007 Session session = new Session(this, root, false /* output to console */); 1008 session.open(Policy.subMonitorFor(monitor, 50), false /* read-only */); 1009 try { 1010 IStatus status = Command.VERSION.execute(session, this, Policy.subMonitorFor(monitor, 50)); 1011 // Log any non-ok status 1012 if (! status.isOK()) { 1013 CVSProviderPlugin.log(status); 1014 } 1015 } finally { 1016 session.close(); 1017 monitor.done(); 1018 } 1019 } catch (CVSException e) { 1020 // If the validation failed, dispose of any cached info 1021 dispose(); 1022 throw e; 1023 } 1024 } 1025 1026 /** 1027 * Return the server platform type. It will be one of the following: 1028 * UNDETERMINED_PLATFORM: The platform has not been determined 1029 * CVS_SERVER: The platform is regular CVS server 1030 * CVSNT_SERVER: The platform in CVSNT 1031 * If UNDETERMINED_PLATFORM is returned, the platform can be determined 1032 * using the Command.VERSION command. 1033 */ getServerPlatform()1034 public int getServerPlatform() { 1035 return serverPlatform; 1036 } 1037 1038 /** 1039 * This method is called from Command.VERSION to set the platform type. 1040 */ setServerPlaform(int serverType)1041 public void setServerPlaform(int serverType) { 1042 // Second, check the code of the status itself to see if it is NT 1043 switch (serverType) { 1044 case CVS_SERVER: 1045 case CVSNT_SERVER: 1046 case UNKNOWN_SERVER: 1047 case UNSUPPORTED_SERVER: 1048 serverPlatform = serverType; 1049 break; 1050 default: 1051 // We had an error status with no info about the server. 1052 // Mark it as undetermined. 1053 serverPlatform = UNDETERMINED_PLATFORM; 1054 } 1055 } 1056 1057 @Override flushUserInfo()1058 public void flushUserInfo() { 1059 removeNode(); 1060 } 1061 1062 /* 1063 * Return the command string that is to be used by the EXT connection method. 1064 */ getExtCommand(String password)1065 String[] getExtCommand(String password) throws IOException { 1066 // Get the user specified connection parameters 1067 String CVS_RSH = CVSProviderPlugin.getPlugin().getCvsRshCommand(); 1068 String CVS_RSH_PARAMETERS = CVSProviderPlugin.getPlugin().getCvsRshParameters(); 1069 String CVS_SERVER = CVSProviderPlugin.getPlugin().getCvsServer(); 1070 if(CVS_RSH == null || CVS_SERVER == null) { 1071 throw new IOException(CVSMessages.EXTServerConnection_varsNotSet); 1072 } 1073 1074 // If there is only one token, assume it is the command and use the default parameters and order 1075 if (CVS_RSH_PARAMETERS == null || CVS_RSH_PARAMETERS.length() == 0) { 1076 if (port != USE_DEFAULT_PORT) 1077 throw new IOException(CVSMessages.EXTServerConnection_invalidPort); 1078 return new String[] {CVS_RSH, host, "-l", user, CVS_SERVER, INVOKE_SVR_CMD}; //$NON-NLS-1$ 1079 } 1080 1081 // Substitute any variables for their appropriate values 1082 CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, USER_VARIABLE, user); 1083 CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, PASSWORD_VARIABLE, password); 1084 CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, HOST_VARIABLE, host); 1085 CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, PORT_VARIABLE, Integer.valueOf(port).toString()); 1086 1087 // Build the command list to be sent to the OS. 1088 List<String> commands = new ArrayList<>(); 1089 commands.add(CVS_RSH); 1090 StringTokenizer tokenizer = new StringTokenizer(CVS_RSH_PARAMETERS); 1091 while (tokenizer.hasMoreTokens()) { 1092 String next = tokenizer.nextToken(); 1093 commands.add(next); 1094 } 1095 commands.add(CVS_SERVER); 1096 commands.add(INVOKE_SVR_CMD); 1097 return commands.toArray(new String[commands.size()]); 1098 } 1099 1100 /* 1101 * Replace all occurrences of oldString with newString 1102 */ stringReplace(String string, String oldString, String newString)1103 private String stringReplace(String string, String oldString, String newString) { 1104 int index = string.toLowerCase().indexOf(oldString); 1105 if (index == -1) return string; 1106 return stringReplace( 1107 string.substring(0, index) + newString + string.substring(index + oldString.length()), 1108 oldString, newString); 1109 } 1110 1111 /** 1112 * Return the server message with the prefix removed. 1113 * Server aborted messages typically start with 1114 * "cvs server: ..." 1115 * "cvs [server aborted]: ..." 1116 * "cvs rtag: ..." 1117 */ getServerMessageWithoutPrefix(String errorLine, String prefix)1118 public String getServerMessageWithoutPrefix(String errorLine, String prefix) { 1119 String message = errorLine; 1120 int firstSpace = message.indexOf(' '); 1121 if(firstSpace != -1) { 1122 // remove the program name and the space 1123 message = message.substring(firstSpace + 1); 1124 // Quick fix to handle changes in server message format (see Bug 45138) 1125 if (prefix.startsWith("[")) { //$NON-NLS-1$ 1126 // This is the server aborted message 1127 // Remove the pattern "[command_name aborted]: " 1128 int closingBracket = message.indexOf("]: "); //$NON-NLS-1$ 1129 if (closingBracket == -1) return null; 1130 // get what is inside the brackets 1131 String realPrefix = message.substring(1, closingBracket); 1132 // check that there is two words and the second word is "aborted" 1133 int space = realPrefix.indexOf(' '); 1134 if (space == -1) return null; 1135 if (realPrefix.indexOf(' ', space +1) != -1) return null; 1136 if (!realPrefix.substring(space +1).equals("aborted")) return null; //$NON-NLS-1$ 1137 // It's a match, return the rest of the line 1138 message = message.substring(closingBracket + 2); 1139 if (message.charAt(0) == ' ') { 1140 message = message.substring(1); 1141 } 1142 return message; 1143 } else { 1144 // This is the server command message 1145 // Remove the pattern "command_name: " 1146 int colon = message.indexOf(": "); //$NON-NLS-1$ 1147 if (colon == -1) return null; 1148 // get what is before the colon 1149 String realPrefix = message.substring(0, colon); 1150 // ensure that it is a single word 1151 if (realPrefix.indexOf(' ') != -1) return null; 1152 message = message.substring(colon + 1); 1153 if (message.charAt(0) == ' ') { 1154 message = message.substring(1); 1155 } 1156 return message; 1157 } 1158 } 1159 // This is not a server message with the desired prefix 1160 return null; 1161 } 1162 1163 @Override getUserAuthenticator()1164 public IUserAuthenticator getUserAuthenticator() { 1165 return getAuthenticator(); 1166 } 1167 1168 @Override setUserAuthenticator(IUserAuthenticator authenticator)1169 public void setUserAuthenticator(IUserAuthenticator authenticator) { 1170 CVSRepositoryLocation.authenticator = authenticator; 1171 } 1172 1173 /* 1174 * Return the preferences node for this repository 1175 */ getPreferences()1176 public Preferences getPreferences() { 1177 if (!hasPreferences()) { 1178 ensurePreferencesStored(); 1179 } 1180 return internalGetPreferences(); 1181 } 1182 internalGetPreferences()1183 private Preferences internalGetPreferences() { 1184 return getParentPreferences().node(getPreferenceName()); 1185 } 1186 hasPreferences()1187 private boolean hasPreferences() { 1188 try { 1189 return getParentPreferences().nodeExists(getPreferenceName()); 1190 } catch (BackingStoreException e) { 1191 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_74, new String[] { getLocation(true) }), e); 1192 return false; 1193 } 1194 } 1195 1196 /** 1197 * Return a unique name that identifies this location but 1198 * does not contain any slashes (/). Also, do not use ':'. 1199 * Although a valid path character, the initial core implementation 1200 * didn't handle it well. 1201 */ getPreferenceName()1202 private String getPreferenceName() { 1203 return getLocation().replace('/', '%').replace(':', '%'); 1204 } 1205 storePreferences()1206 public void storePreferences() { 1207 Preferences prefs = internalGetPreferences(); 1208 // Must store at least one preference in the node 1209 prefs.put(PREF_LOCATION, getLocation()); 1210 flushPreferences(); 1211 } 1212 flushPreferences()1213 private void flushPreferences() { 1214 try { 1215 internalGetPreferences().flush(); 1216 } catch (BackingStoreException e) { 1217 CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_75, new String[] { getLocation(true) }), e); 1218 } 1219 } 1220 ensurePreferencesStored()1221 private void ensurePreferencesStored() { 1222 if (!hasPreferences()) { 1223 storePreferences(); 1224 } 1225 } 1226 1227 @Override getUserInfoCached()1228 public boolean getUserInfoCached() { 1229 ISecurePreferences node = getCVSNode(); 1230 if (node == null) 1231 return false; 1232 try { 1233 String password = node.get(PASSWORD_KEY, null); 1234 return (password != null); 1235 } catch (StorageException e) { // most likely invalid keyring password or corrupted data 1236 CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e); 1237 } 1238 return false; 1239 } 1240 1241 /** 1242 * At this time information is saved in a simplistic flat form. In future, this 1243 * can be modified into a hierarchy of storing information in "connections" 1244 * where "connection" would combine "server" and "account" information (allowing 1245 * user to have the same password for different connections on the server). 1246 * 1247 * Hopefully, we'll get some simplified notion of "account" from Higgins into Equinox 1248 * and then we'll be able to re-use it. 1249 * 1250 * For now, the structure is rather simple: 1251 * node: "CVS" "/CVS/" 1252 * node: account_name name combines all attributes 1253 * | value: login 1254 * | value: password 1255 */ getCVSNode()1256 private ISecurePreferences getCVSNode() { 1257 ISecurePreferences preferences = SecurePreferencesFactory.getDefault(); 1258 if (preferences == null) 1259 return null; 1260 String accountName = EncodingUtils.encodeSlashes(getLocation(true)); 1261 String path = cvsNameSegment + accountName; 1262 try { 1263 return preferences.node(path); 1264 } catch (IllegalArgumentException e) { 1265 return null; // invalid path 1266 } 1267 } 1268 } 1269