001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.subversion.config;
042:
043: import java.io.File;
044: import java.io.FileNotFoundException;
045: import java.io.FileReader;
046: import java.io.IOException;
047: import java.util.ArrayList;
048: import java.util.Iterator;
049: import java.util.List;
050: import java.util.StringTokenizer;
051: import java.util.logging.Level;
052: import java.util.prefs.Preferences;
053: import java.util.regex.Matcher;
054: import java.util.regex.Pattern;
055: import org.ini4j.Ini;
056: import org.netbeans.modules.subversion.Subversion;
057: import org.netbeans.modules.subversion.util.FileUtils;
058: import org.netbeans.modules.subversion.util.ProxySettings;
059: import org.netbeans.modules.subversion.util.SvnUtils;
060: import org.openide.filesystems.FileUtil;
061: import org.openide.util.Utilities;
062: import org.tigris.subversion.svnclientadapter.SVNUrl;
063:
064: /**
065: *
066: * Handles the Subversions <b>servers</b> and <b>config</b> configuration files.</br>
067: * Everytime the singleton instance is created are the values from the commandline clients
068: * configuration directory merged into the Subversion modules configuration files.
069: * (registry on windows are ignored).
070: * Already present proxy setting values wan't be changed,
071: * the remaining values are always taken from the commandline clients configuration files.
072: * The only exception is the 'store-auth-creds' key, which is always set to 'no'.
073: *
074: * @author Tomas Stupka
075: */
076: public class SvnConfigFiles {
077:
078: /** the only SvnConfigFiles instance */
079: private static SvnConfigFiles instance;
080:
081: /** the Ini instance holding the configuration values stored in the <b>servers</b>
082: * file used by the Subversion module */
083: private Ini svnServers = null;
084:
085: /** the Ini instance holding the configuration values stored in the <b>config</b>
086: * file used by the Subversion module */
087: private Ini config = null;
088:
089: private ProxySettings proxySettings;
090:
091: private static final String UNIX_CONFIG_DIR = ".subversion/"; // NOI18N
092: private static final String GROUPS_SECTION = "groups"; // NOI18N
093: private static final String GLOBAL_SECTION = "global"; // NOI18N
094: private static final String WINDOWS_USER_APPDATA = getAPPDATA();
095: private static final String WINDOWS_CONFIG_DIR = WINDOWS_USER_APPDATA
096: + "\\Subversion"; // NOI18N
097: private static final String WINDOWS_GLOBAL_CONFIG_DIR = getGlobalAPPDATA()
098: + "\\Subversion"; // NOI18N
099: private static final List<String> DEFAULT_GLOBAL_IGNORES = parseGlobalIgnores("*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store"); // NOI18N
100:
101: private interface IniFilePatcher {
102: void patch(Ini file);
103: }
104:
105: /**
106: * The value for the 'store-auth-creds' key in the config cofiguration file is alway set to 'no'
107: * so the commandline client wan't create a file holding the authentication credentials when
108: * a svn command is called. The reason for this is that the Subverion module holds the credentials
109: * in files with the same format as the commandline client but with a different name.
110: */
111: private class ConfigIniFilePatcher implements IniFilePatcher {
112: public void patch(Ini file) {
113: // patch store-auth-creds to "no"
114: Ini.Section auth = (Ini.Section) file.get("auth"); // NOI18N
115: if (auth == null) {
116: auth = file.add("auth"); // NOI18N
117: }
118: auth.put("store-auth-creds", "no"); // NOI18N
119: }
120: }
121:
122: /**
123: * Creates a new instance
124: */
125: private SvnConfigFiles() {
126: // copy config file
127: config = copyConfigFileToIDEConfigDir("config",
128: new ConfigIniFilePatcher()); // NOI18N
129: // get the system servers file
130: svnServers = loadSystemIniFile("servers");
131: }
132:
133: /**
134: * Returns a singleton instance.
135: *
136: * @return the SvnConfigFiles instance
137: */
138: public static SvnConfigFiles getInstance() {
139:
140: //T9Y - singleton is not required - always create new instance of this class
141: String t9yUserConfigPath = System
142: .getProperty("netbeans.t9y.svn.user.config.path");
143: if (t9yUserConfigPath != null && t9yUserConfigPath.length() > 0) {
144: //make sure that new instance will be created
145: instance = null;
146: }
147:
148: if (instance == null) {
149: instance = new SvnConfigFiles();
150: }
151: return instance;
152: }
153:
154: /**
155: * Stores the proxy host, port, username and password from the given
156: * {@link org.netbeans.modules.subversion.config.ProxyDescriptor} in the
157: * <b>servers</b> file used by the Subversion module.
158: *
159: * @param host the host
160: */
161: public void setProxy(SVNUrl url) {
162:
163: assert url != null : "can\'t do anything for a null host"; // NOI18N
164:
165: if (!(url.getProtocol().startsWith("http") || url.getProtocol()
166: .startsWith("https"))) {
167: // a proxy will be needed only for remote http and https repositories
168: return;
169: }
170:
171: String host = SvnUtils.ripUserFromHost(url.getHost());
172: ProxySettings ps = new ProxySettings();
173: if (proxySettings != null && ps.equals(proxySettings)) {
174: return;
175: } else {
176: proxySettings = ps;
177: }
178:
179: Ini nbServers = new Ini();
180: Ini.Section nbGlobalSection = nbServers.add(GLOBAL_SECTION);
181: Ini.Section svnGlobalSection = svnServers.get(GLOBAL_SECTION);
182: if (!proxySettings.isDirect()) {
183: String proxyHost = "";
184: int proxyPort = -1;
185: if (url.getProtocol().startsWith("https")) {
186: proxyHost = proxySettings.getHttpsHost();
187: proxyPort = proxySettings.getHttpsPort();
188: }
189: if (proxyHost.equals("")) {
190: proxyHost = proxySettings.getHttpHost();
191: proxyPort = proxySettings.getHttpPort();
192: }
193: String exceptions = proxySettings.getNotProxyHosts();
194:
195: if (proxyHost != null && !proxyHost.equals("")) {
196: nbGlobalSection.put("http-proxy-host", proxyHost); // NOI18N
197: nbGlobalSection.put("http-proxy-port", Integer
198: .toString(proxyPort)); // NOI18N
199: if (!exceptions.equals("")) {
200: nbGlobalSection.put("http-proxy-exceptions",
201: exceptions); // NOI18N
202: }
203:
204: // and the authentication
205: Preferences prefs = org.openide.util.NbPreferences
206: .root().node("org/netbeans/core"); // NOI18N
207: boolean useAuth = prefs.getBoolean(
208: "useProxyAuthentication", false); // NOI18N
209: if (useAuth) {
210: String username = prefs.get(
211: "proxyAuthenticationUsername", ""); // NOI18N
212: String password = prefs.get(
213: "proxyAuthenticationPassword", ""); // NOI18N
214:
215: nbGlobalSection
216: .put("http-proxy-username", username); // NOI18N
217: nbGlobalSection
218: .put("http-proxy-password", password); // NOI18N
219: }
220: }
221: }
222: // check if there are also some no proxy settings
223: // we should get from the original svn servers file
224: mergeNonProxyKeys(host, svnGlobalSection, nbGlobalSection);
225:
226: storeIni(nbServers, "servers"); // NOI18N
227: }
228:
229: private void mergeNonProxyKeys(String host,
230: Ini.Section svnGlobalSection, Ini.Section nbGlobalSection) {
231: if (svnGlobalSection != null) {
232: // if there is a global section, than get the no proxy settings
233: mergeNonProxyKeys(svnGlobalSection, nbGlobalSection);
234: }
235: Ini.Section svnHostGroup = getServerGroup(host);
236: if (svnHostGroup != null) {
237: // if there is a section for the given host, than get the no proxy settings
238: mergeNonProxyKeys(svnHostGroup, nbGlobalSection);
239: }
240: }
241:
242: private void mergeNonProxyKeys(Ini.Section source,
243: Ini.Section target) {
244: for (String key : source.keySet()) {
245: if (!isProxyConfigurationKey(key)) {
246: target.put(key, source.get(key));
247: }
248: }
249: }
250:
251: public void setExternalCommand(String tunnelName, String command) {
252: Ini.Section tunnels = getSection(config, "tunnels", true);
253: tunnels.put(tunnelName, command);
254: storeIni(config, "config"); // NOI18N
255: }
256:
257: public String getExternalCommand(String tunnelName) {
258: Ini.Section tunnels = getSection(config, "tunnels", true);
259: String cmd = tunnels.get(tunnelName);
260: return cmd != null ? cmd : "";
261: }
262:
263: private Ini.Section getSection(Ini ini, String key, boolean create) {
264: Ini.Section section = ini.get(key);
265: if (section == null) {
266: return ini.add(key);
267: }
268: return section;
269: }
270:
271: private void storeIni(Ini ini, String iniFile) {
272: try {
273: File file = FileUtil.normalizeFile(new File(
274: getNBConfigPath() + "/" + iniFile)); // NOI18N
275: file.getParentFile().mkdirs();
276: ini.store(FileUtils.createOutputStream(file));
277: } catch (IOException ex) {
278: Subversion.LOG.log(Level.INFO, null, ex);
279: }
280: }
281:
282: /**
283: * Returns the miscellany/global-ignores setting from the config file.
284: *
285: * @return a list with the inore patterns
286: *
287: */
288: public List<String> getGlobalIgnores() {
289: Ini.Section miscellany = config.get("miscellany"); // NOI18N
290: if (miscellany != null) {
291: String ignores = miscellany.get("global-ignores"); // NOI18N
292: if (ignores != null && ignores.trim().length() > 0) {
293: return parseGlobalIgnores(ignores);
294: }
295: }
296: return DEFAULT_GLOBAL_IGNORES;
297: }
298:
299: public String getClientCertFile(String host) {
300: return getMergeValue("ssl-client-cert-file", host); // NOI18N
301: }
302:
303: public String getClientCertPassword(String host) {
304: return getMergeValue("ssl-client-cert-password", host); // NOI18N
305: }
306:
307: private String getMergeValue(String key, String host) {
308: Ini.Section group = getServerGroup(host);
309: if (group != null) {
310: return group.get(key);
311: }
312: group = svnServers.get(GLOBAL_SECTION);
313: if (group != null) {
314: return group.get(key);
315: }
316: return null;
317: }
318:
319: private static List<String> parseGlobalIgnores(String ignores) {
320: StringTokenizer st = new StringTokenizer(ignores, " "); // NOI18N
321: List<String> ret = new ArrayList<String>(10);
322: while (st.hasMoreTokens()) {
323: String entry = st.nextToken();
324: if (!entry.equals("")) // NOI18N
325: ret.add(entry);
326: }
327: return ret;
328: }
329:
330: /**
331: * Returns the path for the Sunbversion configuration dicectory used
332: * by the systems Subversion commandline client.
333: *
334: * @return the path
335: *
336: */
337: public static String getUserConfigPath() {
338:
339: //T9Y - user svn config files should be changable
340: String t9yUserConfigPath = System
341: .getProperty("netbeans.t9y.svn.user.config.path");
342: if (t9yUserConfigPath != null && t9yUserConfigPath.length() > 0) {
343: return t9yUserConfigPath;
344: }
345:
346: if (Utilities.isUnix()) {
347: String path = System.getProperty("user.home"); // NOI18N
348: return path + "/" + UNIX_CONFIG_DIR; // NOI18N
349: } else if (Utilities.isWindows()) {
350: return WINDOWS_CONFIG_DIR;
351: }
352: return ""; // NOI18N
353: }
354:
355: /**
356: * Returns the path for the Sunbversion configuration directory used
357: * by the Netbeans Subversion module.
358: *
359: * @return the path
360: *
361: */
362: public static String getNBConfigPath() {
363:
364: //T9Y - nb svn confing should be changable
365: String t9yNbConfigPath = System
366: .getProperty("netbeans.t9y.svn.nb.config.path");
367: if (t9yNbConfigPath != null && t9yNbConfigPath.length() > 0) {
368: return t9yNbConfigPath;
369: }
370:
371: String nbHome = System.getProperty("netbeans.user"); // NOI18N
372: return nbHome + "/config/svn/config/"; // NOI18N
373: }
374:
375: /**
376: * Returns the section from the <b>servers</b> config file used by the Subversion module which
377: * is holding the proxy settings for the given host
378: *
379: * @param host the host
380: * @return the section holding the proxy settings for the given host
381: */
382: private Ini.Section getServerGroup(String host) {
383: if (host == null || host.equals("")) { // NOI18N
384: return null;
385: }
386: Ini.Section groups = svnServers.get(GROUPS_SECTION);
387: if (groups != null) {
388: for (Iterator<String> it = groups.keySet().iterator(); it
389: .hasNext();) {
390: String key = it.next();
391: String value = groups.get(key);
392: if (value != null) {
393: // XXX the same pattern everywhere when calling match()
394: value = value.trim();
395: if (value != null && match(value, host)) {
396: return svnServers.get(key);
397: }
398: }
399: }
400: }
401: return null;
402: }
403:
404: /**
405: * Evaluates if the given hostaname or IP address is in the given value String.
406: *
407: * @param value the value String. A list of host names or IP addresses delimited by ",".
408: * (e.g 192.168.0.1,*.168.0.1, some.domain.com, *.anything.com, ...)
409: * @param host the hostname or IP address
410: * @return true if the host name or IP address was found in the values String, otherwise false.
411: */
412: private boolean match(String value, String host) {
413: String[] values = value.split(","); // NOI18N
414: for (int i = 0; i < values.length; i++) {
415: value = values[i].trim();
416:
417: if (value.equals("*") || value.equals(host)) { // NOI18N
418: return true;
419: }
420:
421: int idx = value.indexOf("*"); // NOI18N
422: if (idx > -1 && matchSegments(value, host)) {
423: return true;
424: }
425: }
426: return false;
427: }
428:
429: /**
430: * Evaluates if the given hostaname or IP address matches with the given value String representing
431: * a hostaname or IP adress with one or more "*" wildcards in it.
432: *
433: * @param value the value String. A host name or IP addresse with a "*" wildcard. (e.g *.168.0.1 or *.anything.com)
434: * @param host the hostname or IP address
435: * @return true if the host name or IP address matches with the values String, otherwise false.
436: */
437: private boolean matchSegments(String value, String host) {
438: value = value.replace(".", "\\.");
439: value = value.replace("*", ".*");
440: Matcher m = Pattern.compile(value).matcher(host);
441: return m.matches();
442: }
443:
444: /**
445: * Copies the given configuration file from the Subversion commandline client
446: * configuration directory into the configuration directory used by the Netbeans Subversion module. </br>
447: */
448: private Ini copyConfigFileToIDEConfigDir(String fileName,
449: IniFilePatcher patcher) {
450: Ini systemIniFile = loadSystemIniFile(fileName);
451:
452: patcher.patch(systemIniFile);
453:
454: File file = FileUtil.normalizeFile(new File(getNBConfigPath()
455: + "/" + fileName)); // NOI18N
456: try {
457: file.getParentFile().mkdirs();
458: systemIniFile.store(FileUtils.createOutputStream(file));
459: } catch (IOException ex) {
460: Subversion.LOG.log(Level.INFO, null, ex); // should not happen
461: }
462: return systemIniFile;
463: }
464:
465: /**
466: * Loads the ini configuration file from the directory used by
467: * the Subversion commandline client. The settings are loaded and merged together in
468: * in the folowing order:
469: * <ol>
470: * <li> The per-user INI files
471: * <li> The system-wide INI files
472: * </ol>
473: *
474: * @param fileName the file name
475: * @return an Ini instance holding the cofiguration file.
476: */
477: private Ini loadSystemIniFile(String fileName) {
478: // config files from userdir
479: String filePath = getUserConfigPath() + "/" + fileName; // NOI18N
480: File file = FileUtil.normalizeFile(new File(filePath));
481: Ini system = null;
482: try {
483: system = new Ini(new FileReader(file));
484: } catch (FileNotFoundException ex) {
485: // ignore
486: } catch (IOException ex) {
487: Subversion.LOG.log(Level.INFO, null, ex);
488: }
489:
490: if (system == null) {
491: system = new Ini();
492: Subversion.LOG.warning("Could not load the file "
493: + filePath + ". Falling back on svn defaults."); // NOI18N
494: }
495:
496: Ini global = null;
497: try {
498: global = new Ini(new FileReader(getGlobalConfigPath() + "/"
499: + fileName)); // NOI18N
500: } catch (FileNotFoundException ex) {
501: // just doesn't exist - ignore
502: } catch (IOException ex) {
503: Subversion.LOG.log(Level.INFO, null, ex);
504: }
505:
506: if (global != null) {
507: merge(global, system);
508: }
509: return system;
510: }
511:
512: /**
513: * Merges only sections/keys/values into target which are not already present in source
514: *
515: * @param source the source ini file
516: * @param target the target ini file in which the values from the source file are going to be merged
517: */
518: private void merge(Ini source, Ini target) {
519: for (Iterator<String> itSections = source.keySet().iterator(); itSections
520: .hasNext();) {
521: String sectionName = itSections.next();
522: Ini.Section sourceSection = source.get(sectionName);
523: Ini.Section targetSection = target.get(sectionName);
524:
525: if (targetSection == null) {
526: targetSection = target.add(sectionName);
527: }
528:
529: for (Iterator<String> itVariables = sourceSection.keySet()
530: .iterator(); itVariables.hasNext();) {
531: String key = itVariables.next();
532:
533: if (!targetSection.containsKey(key)) {
534: targetSection.put(key, sourceSection.get(key));
535: }
536: }
537: }
538: }
539:
540: /**
541: * Evaluates if the value stored under the key is a proxy setting value.
542: *
543: * @param key the key
544: * @return true if the value stored under the key is a proxy setting value. Otherwise false
545: */
546: private boolean isProxyConfigurationKey(String key) {
547: return key.equals("http-proxy-host") || // NOI18N
548: key.equals("http-proxy-port") || // NOI18N
549: key.equals("http-proxy-username") || // NOI18N
550: key.equals("http-proxy-password") || // NOI18N
551: key.equals("http-proxy-exceptions"); // NOI18N
552: }
553:
554: /**
555: * Return the path for the systemwide command lines configuration directory
556: */
557: private static String getGlobalConfigPath() {
558: if (Utilities.isUnix()) {
559: return "/etc/subversion"; // NOI18N
560: } else if (Utilities.isWindows()) {
561: return WINDOWS_GLOBAL_CONFIG_DIR;
562: }
563: return ""; // NOI18N
564: }
565:
566: /**
567: * Returns the value for the %APPDATA% env variable on windows
568: *
569: */
570: private static String getAPPDATA() {
571: String appdata = "";
572: if (Utilities.isWindows()) {
573: appdata = System.getenv("APPDATA");// NOI18N
574: }
575: return appdata != null ? appdata : "";
576: }
577:
578: /**
579: * Returns the value for the %ALLUSERSPROFILE% + the last foder segment from %APPDATA% env variables on windows
580: *
581: */
582: private static String getGlobalAPPDATA() {
583: if (Utilities.isWindows()) {
584: String globalProfile = System.getenv("ALLUSERSPROFILE"); // NOI18N
585: if (globalProfile == null
586: || globalProfile.trim().equals("")) { // NOI18N
587: globalProfile = "";
588: }
589: String appdataPath = WINDOWS_USER_APPDATA;
590: if (appdataPath == null || appdataPath.equals("")) { // NOI18N
591: return ""; // NOI18N
592: }
593: String appdata = ""; // NOI18N
594: int idx = appdataPath.lastIndexOf("\\"); // NOI18N
595: if (idx > -1) {
596: appdata = appdataPath.substring(idx + 1);
597: if (appdata.trim().equals("")) { // NOI18N
598: int previdx = appdataPath.lastIndexOf("\\", idx); // NOI18N
599: if (idx > -1) {
600: appdata = appdataPath.substring(previdx + 1,
601: idx);
602: }
603: }
604: } else {
605: return ""; // NOI18N
606: }
607: return globalProfile + "/" + appdata; // NOI18N
608: }
609: return ""; // NOI18N
610: }
611:
612: }
|