org.netbeans.modules.subversion.config.SvnConfigFiles Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.subversion.config;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.ini4j.Config;
import org.ini4j.Ini;
import org.netbeans.modules.subversion.Subversion;
import org.netbeans.modules.subversion.SvnModuleConfig;
import org.netbeans.modules.subversion.client.SvnClientFactory.ConnectionType;
import org.netbeans.modules.subversion.ui.repository.RepositoryConnection;
import org.netbeans.modules.versioning.util.FileUtils;
import org.netbeans.modules.subversion.util.SvnUtils;
import org.netbeans.modules.versioning.util.KeyringSupport;
import org.openide.filesystems.FileUtil;
import org.openide.modules.Places;
import org.openide.util.NetworkSettings;
import org.openide.util.Utilities;
import org.tigris.subversion.svnclientadapter.SVNUrl;
/**
*
* Handles the Subversions servers and config configuration files.
* Everytime the singleton instance is created are the values from the commandline clients
* configuration directory merged into the Subversion modules configuration files.
* (registry on windows are ignored).
* Already present proxy setting values wan't be changed,
* the remaining values are always taken from the commandline clients configuration files.
* The only exception is the 'store-auth-creds' key, which is always set to 'no'.
*
* @author Tomas Stupka
*/
public class SvnConfigFiles {
/** the only SvnConfigFiles instance */
private static SvnConfigFiles instance;
/** the Ini instance holding the configuration values stored in the servers
* file used by the Subversion module */
private Ini svnServers = null;
/** the Ini instance holding the configuration values stored in the config
* file used by the Subversion module */
private Ini config = null;
private static final String UNIX_CONFIG_DIR = ".subversion/"; // NOI18N
private static final String GROUPS_SECTION = "groups"; // NOI18N
private static final String GLOBAL_SECTION = "global"; // NOI18N
private static final String WINDOWS_USER_APPDATA = getAPPDATA();
private static final String WINDOWS_CONFIG_DIR = WINDOWS_USER_APPDATA + "\\Subversion"; // NOI18N
private static final String WINDOWS_GLOBAL_CONFIG_DIR = getGlobalAPPDATA() + "\\Subversion"; // NOI18N
private static final List DEFAULT_GLOBAL_IGNORES =
parseGlobalIgnores("*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store"); // NOI18N
private static final boolean DO_NOT_SAVE_PASSPHRASE = Boolean.getBoolean("versioning.subversion.noPassphraseInConfig"); // NOI18N
private String recentUrl;
private interface IniFilePatcher {
void patch(Ini file);
}
/**
* The value for the 'store-auth-creds' key in the config cofiguration file is alway set to 'no'
* so the commandline client wan't create a file holding the authentication credentials when
* a svn command is called. The reason for this is that the Subverion module holds the credentials
* in files with the same format as the commandline client but with a different name.
*
* Also sets password-stores to empty value. We currently handle password stores poorly and occasionally non-empty values cause a deadlock (see #178122).
*/
private class ConfigIniFilePatcher implements IniFilePatcher {
@Override
public void patch(Ini file) {
// patch store-auth-creds to "no"
Ini.Section auth = (Ini.Section) file.get("auth"); // NOI18N
if(auth == null) {
auth = file.add("auth"); // NOI18N
}
auth.put("store-auth-creds", "yes"); // NOI18N
auth.put("store-passwords", "no"); // NOI18N
auth.put("password-stores", ""); // NOI18N
}
}
/**
* Creates a new instance
*/
private SvnConfigFiles() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(Subversion.class.getClassLoader());
try {
Config.getGlobal().setEscape(false); // do not escape characters
// copy config file
config = copyConfigFileToIDEConfigDir("config", new ConfigIniFilePatcher()); // NOI18N
// get the system servers file
svnServers = loadSystemIniFile("servers");
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
// SvnModuleConfig.getDefault().getPreferences().addPreferenceChangeListener(this);
}
/**
* Returns a singleton instance.
*
* @return the SvnConfigFiles instance
*/
public static synchronized SvnConfigFiles getInstance() {
//T9Y - singleton is not required - always create new instance of this class
String t9yUserConfigPath = System.getProperty("netbeans.t9y.svn.user.config.path");
if (t9yUserConfigPath != null && t9yUserConfigPath.length() > 0) {
//make sure that new instance will be created
instance = null;
}
if(instance == null) {
instance = new SvnConfigFiles();
}
return instance;
}
public void reset() {
recentUrl = null; // force rewrite
}
/**
* Stores the cert file and password, proxy host, port, username and password for the given
* {@link SVNUrl} in the
* servers file used by the Subversion module.
*
* It returns an instance of config file that should be deleted as soon as possible
* because it contains sensitive private data such as passwords or passphrases.
*
* @param host the host
*/
public File storeSvnServersSettings(SVNUrl url, ConnectionType connType) {
assert url != null : "can\'t do anything for a null host"; // NOI18N
File sensitiveConfigFile = null;
if(!(url.getProtocol().startsWith("http") || //NOI18N
url.getProtocol().startsWith("https") || //NOI18N
url.getProtocol().startsWith("svn+")) ) //NOI18N
{
// we need the settings only for remote http and https repositories
return sensitiveConfigFile;
}
boolean changes = false;
Ini nbServers = new Ini();
Ini.Section nbGlobalSection = nbServers.add(GLOBAL_SECTION);
String repositoryUrl = url.toString();
changes = !repositoryUrl.equals(recentUrl);
if(changes) {
RepositoryConnection rc = SvnModuleConfig.getDefault().getRepositoryConnection(repositoryUrl);
if (rc != null && url.getProtocol().startsWith("svn+")) { //NOI18N
// must set tunnel info for the repository url
if (connType == ConnectionType.svnkit) {
// hack for svnkit and ssh
// ssh port is read only from ssh tunnel info and considered valid only when usernam and password are not empty
// see implementation in SvnKit: org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager.getDefaultSSHAuthentication()
// weird and ugly
setExternalCommand("ssh", rc.getSshPortNumber() > 0 ? "ssh -p " + rc.getSshPortNumber() + " -P " + rc.getSshPortNumber() + " -l user -pw password" : "");
nbGlobalSection.put("store-auth-creds", "yes"); // NOI18N
nbGlobalSection.put("store-passwords", "no"); // NOI18N
} else {
setExternalCommand(SvnUtils.getTunnelName(url.getProtocol()), rc.getExternalCommand());
}
}
boolean hasPassphrase = false;
if(url.getProtocol().startsWith("https")) {
hasPassphrase = setSSLCert(rc, nbGlobalSection);
}
hasPassphrase = setProxy(url, nbGlobalSection) | hasPassphrase;
File configFile = storeIni(nbServers, "servers"); //NOI18N
recentUrl = url.toString();
if (hasPassphrase) {
sensitiveConfigFile = configFile;
recentUrl = null; //must be regenerated on next run
}
}
return sensitiveConfigFile;
}
private boolean setSSLCert(RepositoryConnection rc, Ini.Section nbGlobalSection) {
if(rc == null) {
return false;
}
String certFile = rc.getCertFile();
if(certFile == null || certFile.equals("")) {
return false;
}
char[] certPasswordChars = rc.getCertPassword();
String certPassword = certPasswordChars == null ? "" : new String(certPasswordChars); //NOI18N
if(certPassword.equals("")) { // NOI18N
return false;
}
nbGlobalSection.put("ssl-client-cert-file", certFile);
if (!DO_NOT_SAVE_PASSPHRASE) {
nbGlobalSection.put("ssl-client-cert-password", certPassword);
return true;
}
return false;
}
private boolean setProxy(SVNUrl url, Ini.Section nbGlobalSection) {
String host = SvnUtils.ripUserFromHost(url.getHost());
Ini.Section svnGlobalSection = svnServers.get(GLOBAL_SECTION);
URI uri = null;
boolean passwordAdded = false;
try {
uri = new URI(url.toString());
} catch (URISyntaxException ex) {
Subversion.LOG.log(Level.INFO, null, ex);
return passwordAdded;
}
String proxyHost = NetworkSettings.getProxyHost(uri);
// check DIRECT connection
if(proxyHost != null && proxyHost.length() > 0) {
String proxyPort = NetworkSettings.getProxyPort(uri);
assert proxyPort != null;
nbGlobalSection.put("http-proxy-host", proxyHost); // NOI18N
nbGlobalSection.put("http-proxy-port", proxyPort); // NOI18N
// and the authentication
String username = NetworkSettings.getAuthenticationUsername(uri);
if(username != null) {
String password = getProxyPassword(NetworkSettings.getKeyForAuthenticationPassword(uri));
nbGlobalSection.put("http-proxy-username", username); // NOI18N
nbGlobalSection.put("http-proxy-password", password); // NOI18N
passwordAdded = true;
}
}
// check if there are also some no proxy settings
// we should get from the original svn servers file
mergeNonProxyKeys(host, svnGlobalSection, nbGlobalSection);
return passwordAdded;
}
private void mergeNonProxyKeys(String host, Ini.Section svnGlobalSection, Ini.Section nbGlobalSection) {
if(svnGlobalSection != null) {
// if there is a global section, than get the no proxy settings
mergeNonProxyKeys(svnGlobalSection, nbGlobalSection);
}
Ini.Section svnHostGroup = getServerGroup(host);
if(svnHostGroup != null) {
// if there is a section for the given host, than get the no proxy settings
mergeNonProxyKeys(svnHostGroup, nbGlobalSection);
}
}
private void mergeNonProxyKeys(Ini.Section source, Ini.Section target) {
for (String key : source.keySet()) {
if(!isProxyConfigurationKey(key)) {
target.put(key, source.get(key));
}
}
}
public void setExternalCommand(String tunnelName, String command) {
if (command == null) {
return;
}
if (Utilities.isWindows()) {
// tunnel command should contain forward slashes even on windows
command = command.replace("\\", "/"); //NOI18N
}
Ini.Section tunnels = getSection(config, "tunnels", true);
tunnels.put(tunnelName, command);
storeIni(config, "config"); // NOI18N
}
public String getExternalCommand(String tunnelName) {
Ini.Section tunnels = getSection(config, "tunnels", true);
String cmd = tunnels.get(tunnelName);
return cmd != null ? cmd : "";
}
private Ini.Section getSection(Ini ini, String key, boolean create) {
Ini.Section section = ini.get(key);
if(section == null) {
return ini.add(key);
}
return section;
}
private File storeIni (Ini ini, String iniFile) {
BufferedOutputStream bos = null;
File file = FileUtil.normalizeFile(new File(getNBConfigPath() + "/" + iniFile)); // NOI18N
try {
file.getParentFile().mkdirs();
ini.store(bos = FileUtils.createOutputStream(file));
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex);
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex);
}
}
}
return file;
}
/**
* Returns the miscellany/global-ignores setting from the config file.
*
* @return a list with the inore patterns
*
*/
public List getGlobalIgnores() {
Ini.Section miscellany = config.get("miscellany"); // NOI18N
if (miscellany != null) {
String ignores = miscellany.get("global-ignores"); // NOI18N
if (ignores != null && ignores.trim().length() > 0) {
return parseGlobalIgnores(ignores);
}
}
return DEFAULT_GLOBAL_IGNORES;
}
public String getClientCertFile(String host) {
return getMergeValue("ssl-client-cert-file", host); // NOI18N
}
public String getClientCertPassword(String host) {
return getMergeValue("ssl-client-cert-password", host); // NOI18N
}
private String getMergeValue(String key, String host) {
Ini.Section group = getServerGroup(host);
if(group != null) {
return group.get(key);
}
group = svnServers.get(GLOBAL_SECTION);
if(group != null) {
return group.get(key);
}
return null;
}
private static List parseGlobalIgnores(String ignores) {
StringTokenizer st = new StringTokenizer(ignores, " "); // NOI18N
List ret = new ArrayList(10);
while (st.hasMoreTokens()) {
String entry = st.nextToken();
if (!entry.equals("")) // NOI18N
ret.add(entry);
}
return ret;
}
/**
* Returns the path for the Sunbversion configuration dicectory used
* by the systems Subversion commandline client.
*
* @return the path
*
*/
public static String getUserConfigPath() {
//T9Y - user svn config files should be changable
String t9yUserConfigPath = System.getProperty("netbeans.t9y.svn.user.config.path");
if (t9yUserConfigPath != null && t9yUserConfigPath.length() > 0) {
return t9yUserConfigPath;
}
if(Utilities.isUnix()) {
String path = System.getProperty("user.home") ; // NOI18N
return path + "/" + UNIX_CONFIG_DIR; // NOI18N
} else if (Utilities.isWindows()){
return WINDOWS_CONFIG_DIR;
}
return ""; // NOI18N
}
/**
* Returns the path for the Sunbversion configuration directory used
* by the Netbeans Subversion module.
*
* @return the path
*
*/
public static String getNBConfigPath() {
//T9Y - nb svn confing should be changable
String t9yNbConfigPath = System.getProperty("netbeans.t9y.svn.nb.config.path");
if (t9yNbConfigPath != null && t9yNbConfigPath.length() > 0) {
return t9yNbConfigPath;
}
String nbHome = Places.getUserDirectory().getAbsolutePath();
return nbHome + "/config/svn/config/"; // NOI18N
}
/**
* Returns the section from the servers config file used by the Subversion module which
* is holding the proxy settings for the given host
*
* @param host the host
* @return the section holding the proxy settings for the given host
*/
private Ini.Section getServerGroup(String host) {
if(host == null || host.equals("")) { // NOI18N
return null;
}
Ini.Section groups = svnServers.get(GROUPS_SECTION);
if(groups != null) {
for (Iterator it = groups.keySet().iterator(); it.hasNext();) {
String key = it.next();
String value = groups.get(key);
if(value != null) {
value = value.trim();
if(value != null && match(value, host)) {
return svnServers.get(key);
}
}
}
}
return null;
}
/**
* Evaluates if the given hostaname or IP address is in the given value String.
*
* @param value the value String. A list of host names or IP addresses delimited by ",".
* (e.g 192.168.0.1,*.168.0.1, some.domain.com, *.anything.com, ...)
* @param host the hostname or IP address
* @return true if the host name or IP address was found in the values String, otherwise false.
*/
private boolean match(String value, String host) {
String[] values = value.split(","); // NOI18N
for (int i = 0; i < values.length; i++) {
value = values[i].trim();
if(value.equals("*") || value.equals(host) ) { // NOI18N
return true;
}
int idx = value.indexOf("*"); // NOI18N
if(idx > -1 && matchSegments(value, host) ) {
return true;
}
}
return false;
}
/**
* Evaluates if the given hostaname or IP address matches with the given value String representing
* a hostaname or IP adress with one or more "*" wildcards in it.
*
* @param value the value String. A host name or IP addresse with a "*" wildcard. (e.g *.168.0.1 or *.anything.com)
* @param host the hostname or IP address
* @return true if the host name or IP address matches with the values String, otherwise false.
*/
private boolean matchSegments(String value, String host) {
value = value.replace(".", "\\.");
value = value.replace("*", ".*");
Matcher m = Pattern.compile(value).matcher(host);
return m.matches();
}
/**
* Copies the given configuration file from the Subversion commandline client
* configuration directory into the configuration directory used by the Netbeans Subversion module.
*/
private Ini copyConfigFileToIDEConfigDir(String fileName, IniFilePatcher patcher) {
Ini systemIniFile = loadSystemIniFile(fileName);
patcher.patch(systemIniFile);
File file = FileUtil.normalizeFile(new File(getNBConfigPath() + File.separatorChar + fileName)); // NOI18N
BufferedOutputStream bos = null;
try {
file.getParentFile().mkdirs();
systemIniFile.store(bos = FileUtils.createOutputStream(file));
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex) ; // should not happen
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex);
}
}
}
return systemIniFile;
}
/**
* Loads the ini configuration file from the directory used by
* the Subversion commandline client. The settings are loaded and merged together in
* in the folowing order:
*
* - The per-user INI files
*
- The system-wide INI files
*
*
* @param fileName the file name
* @return an Ini instance holding the cofiguration file.
*/
private Ini loadSystemIniFile(String fileName) {
// config files from userdir
String filePath = getUserConfigPath() + "/" + fileName; // NOI18N
File file = FileUtil.normalizeFile(new File(filePath));
Ini system = null;
try {
system = new Ini(new FileReader(file));
} catch (FileNotFoundException ex) {
// ignore
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex) ;
} catch (Exception ex) {
Subversion.LOG.log(Level.INFO, "exception in Ini4j, system file not loaded: " + filePath, ex);
}
if(system == null) {
system = new Ini();
Subversion.LOG.warning("Could not load the file " + filePath + ". Falling back on svn defaults."); // NOI18N
}
Ini global = null;
try {
global = new Ini(new FileReader(getGlobalConfigPath() + "/" + fileName)); // NOI18N
} catch (FileNotFoundException ex) {
// just doesn't exist - ignore
} catch (IOException ex) {
Subversion.LOG.log(Level.INFO, null, ex) ;
} catch (Exception ex) {
Subversion.LOG.log(Level.INFO, "exception in Ini4j, global file not loaded: " + getGlobalConfigPath() + "/" + fileName, ex);
}
if(global != null) {
merge(global, system);
}
return system;
}
/**
* Merges only sections/keys/values into target which are not already present in source
*
* @param source the source ini file
* @param target the target ini file in which the values from the source file are going to be merged
*/
private void merge(Ini source, Ini target) {
for (Iterator itSections = source.keySet().iterator(); itSections.hasNext();) {
String sectionName = itSections.next();
Ini.Section sourceSection = source.get( sectionName );
Ini.Section targetSection = target.get( sectionName );
if(targetSection == null) {
targetSection = target.add(sectionName);
}
for (Iterator itVariables = sourceSection.keySet().iterator(); itVariables.hasNext();) {
String key = itVariables.next();
if(!targetSection.containsKey(key)) {
targetSection.put(key, sourceSection.get(key));
}
}
}
}
/**
* Evaluates if the value stored under the key is a proxy setting value.
*
* @param key the key
* @return true if the value stored under the key is a proxy setting value. Otherwise false
*/
private boolean isProxyConfigurationKey(String key) {
return key.equals("http-proxy-host") || // NOI18N
key.equals("http-proxy-port") || // NOI18N
key.equals("http-proxy-username") || // NOI18N
key.equals("http-proxy-password") || // NOI18N
key.equals("http-proxy-exceptions"); // NOI18N
}
/**
* Return the path for the systemwide command lines configuration directory
*/
private static String getGlobalConfigPath () {
if(Utilities.isUnix()) {
return "/etc/subversion"; // NOI18N
} else if (Utilities.isWindows()){
return WINDOWS_GLOBAL_CONFIG_DIR;
}
return ""; // NOI18N
}
/**
* Returns the value for the %APPDATA% env variable on windows
*
*/
private static String getAPPDATA() {
String appdata = "";
if(Utilities.isWindows()) {
appdata = System.getenv("APPDATA");// NOI18N
}
return appdata!= null? appdata: "";
}
/**
* Returns the value for the %ALLUSERSPROFILE% + the last foder segment from %APPDATA% env variables on windows
*
*/
private static String getGlobalAPPDATA() {
if(Utilities.isWindows()) {
String globalProfile = System.getenv("ALLUSERSPROFILE"); // NOI18N
if(globalProfile == null || globalProfile.trim().equals("")) { // NOI18N
globalProfile = "";
}
String appdataPath = WINDOWS_USER_APPDATA;
if(appdataPath == null || appdataPath.equals("")) { // NOI18N
return ""; // NOI18N
} // NOI18N
return getWinUserAppdata(appdataPath, globalProfile); // NOI18N
}
return ""; // NOI18N
}
private static String getWinUserAppdata(String appdataPath, String globalProfile) {
appdataPath = trimBackslash(appdataPath);
globalProfile = trimBackslash(globalProfile);
String appdata = ""; // NOI18N
int idx = appdataPath.lastIndexOf("\\"); // NOI18N
if (idx > -1) {
appdata = appdataPath.substring(idx + 1);
if (appdata.trim().equals("")) { // NOI18N
int previdx = appdataPath.lastIndexOf("\\", idx); // NOI18N
if (idx > -1) {
appdata = appdataPath.substring(previdx + 1, idx);
}
}
} else {
return ""; // NOI18N
}
if(globalProfile.endsWith("\\")) { // NOI18N
globalProfile = globalProfile.substring(0, globalProfile.length() - 1);
}
return globalProfile + "/" + appdata; // NOI18N
}
private static String trimBackslash(String appdataPath) {
if (appdataPath.endsWith("\\")) {
// NOI18N
appdataPath = appdataPath.substring(0, appdataPath.length() - 1);
}
return appdataPath;
}
private String getProxyPassword(String key) {
char[] pwd = KeyringSupport.read("", key);
return pwd == null ? "" : new String(pwd); //NOI18N
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy