001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.engine;
007:
008: import java.sql.SQLException;
009: import java.util.Arrays;
010: import java.util.HashSet;
011: import java.util.Properties;
012:
013: import org.h2.api.DatabaseEventListener;
014: import org.h2.command.dml.SetTypes;
015: import org.h2.constant.ErrorCode;
016: import org.h2.constant.SysProperties;
017: import org.h2.message.Message;
018: import org.h2.security.SHA256;
019: import org.h2.util.FileUtils;
020: import org.h2.util.MathUtils;
021: import org.h2.util.ObjectArray;
022: import org.h2.util.StringUtils;
023:
024: /**
025: * Encapsulates the connection settings, including user name and password.
026: */
027: public class ConnectionInfo {
028: private static final HashSet KNOWN_SETTINGS = new HashSet();
029: private final Properties prop = new Properties();
030: private String originalURL;
031: private String url;
032: private String user;
033: private byte[] filePasswordHash;
034: private byte[] userPasswordHash;
035: private String name;
036: private boolean remote;
037: private boolean ssl;
038: private boolean persistent;
039: private boolean unnamed;
040:
041: static {
042: ObjectArray list = SetTypes.getSettings();
043: for (int i = 0; i < list.size(); i++) {
044: KNOWN_SETTINGS.add(list.get(i));
045: }
046: // TODO document these settings
047: String[] connectionTime = new String[] { "ACCESS_MODE_LOG",
048: "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER", "CREATE",
049: "CACHE_TYPE", "DB_CLOSE_ON_EXIT", "FILE_LOCK",
050: "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS", "PASSWORD",
051: "RECOVER", "STORAGE", "USER",
052: "DATABASE_EVENT_LISTENER_OBJECT" };
053: for (int i = 0; i < connectionTime.length; i++) {
054: String key = connectionTime[i];
055: if (SysProperties.CHECK && KNOWN_SETTINGS.contains(key)) {
056: throw Message.getInternalError(key);
057: }
058: KNOWN_SETTINGS.add(key);
059: }
060: }
061:
062: public ConnectionInfo(String name) {
063: this .name = name;
064: parseName();
065: }
066:
067: public ConnectionInfo(String u, Properties info)
068: throws SQLException {
069: this .originalURL = u;
070: if (!u.startsWith(Constants.START_URL)) {
071: throw Message.getInvalidValueException(u, "url");
072: }
073: this .url = u;
074: readProperties(info);
075: readSettingsFromURL();
076: setUserName(removeProperty("USER", ""));
077: readPasswords();
078: name = url.substring(Constants.START_URL.length());
079: parseName();
080: }
081:
082: private void parseName() {
083: if (".".equals(name)) {
084: name = "mem:";
085: }
086: if (name.startsWith("tcp:")) {
087: remote = true;
088: name = name.substring("tcp:".length());
089: } else if (name.startsWith("ssl:")) {
090: remote = true;
091: ssl = true;
092: name = name.substring("ssl:".length());
093: } else if (name.startsWith("mem:")) {
094: persistent = false;
095: if ("mem:".equals(name)) {
096: unnamed = true;
097: }
098: } else if (name.startsWith("file:")) {
099: name = name.substring("file:".length());
100: persistent = true;
101: } else {
102: persistent = true;
103: }
104: }
105:
106: public String getDatabaseName() {
107: if (remote) {
108: if (ssl) {
109: return "ssl:" + name;
110: } else {
111: return "tcp:" + name;
112: }
113: } else if (persistent) {
114: return "file:" + name;
115: }
116: return name;
117: }
118:
119: public void setBaseDir(String dir) {
120: if (persistent) {
121: if (!name.startsWith("~")) {
122: name = dir + SysProperties.FILE_SEPARATOR + name;
123: }
124: }
125: }
126:
127: public boolean isRemote() {
128: return remote;
129: }
130:
131: public boolean isPersistent() {
132: return persistent;
133: }
134:
135: public boolean isUnnamed() {
136: return unnamed;
137: }
138:
139: private void readProperties(Properties info) throws SQLException {
140: Object[] list = new Object[info.size()];
141: info.keySet().toArray(list);
142: for (int i = 0; i < list.length; i++) {
143: String key = StringUtils.toUpperEnglish(list[i].toString());
144: if (prop.containsKey(key)) {
145: throw Message.getSQLException(
146: ErrorCode.DUPLICATE_PROPERTY_1, key);
147: }
148: if (KNOWN_SETTINGS.contains(key)) {
149: prop.put(key, info.get(list[i]));
150: }
151: }
152: }
153:
154: private void readSettingsFromURL() throws SQLException {
155: int idx = url.indexOf(';');
156: if (idx >= 0) {
157: String settings = url.substring(idx + 1);
158: url = url.substring(0, idx);
159: String[] list = StringUtils
160: .arraySplit(settings, ';', false);
161: for (int i = 0; i < list.length; i++) {
162: String setting = list[i];
163: int equal = setting.indexOf('=');
164: if (equal < 0) {
165: throw getFormatException();
166: }
167: String value = setting.substring(equal + 1);
168: String key = setting.substring(0, equal);
169: key = StringUtils.toUpperEnglish(key);
170: if (!KNOWN_SETTINGS.contains(key)) {
171: throw Message.getSQLException(
172: ErrorCode.UNSUPPORTED_SETTING_1, key);
173: }
174: String old = prop.getProperty(key);
175: if (old != null && !old.equals(value)) {
176: throw Message.getSQLException(
177: ErrorCode.DUPLICATE_PROPERTY_1, key);
178: }
179: prop.setProperty(key, value);
180: }
181: }
182: }
183:
184: DatabaseEventListener removeDatabaseEventListenerObject()
185: throws SQLException {
186: Object p = prop.remove("DATABASE_EVENT_LISTENER_OBJECT");
187: if (p == null) {
188: return null;
189: }
190: if (p instanceof DatabaseEventListener) {
191: return (DatabaseEventListener) p;
192: }
193: throw Message.getSQLException(
194: ErrorCode.DATA_CONVERSION_ERROR_1, p.getClass()
195: .getName());
196: }
197:
198: private char[] removePassword() {
199: Object p = prop.remove("PASSWORD");
200: if (p == null) {
201: return new char[0];
202: } else if (p instanceof char[]) {
203: return (char[]) p;
204: } else {
205: return p.toString().toCharArray();
206: }
207: }
208:
209: public void readPasswords() throws SQLException {
210: char[] password = removePassword();
211: SHA256 sha = new SHA256();
212: if (getProperty("CIPHER", null) != null) {
213: // split password into (filePassword+' '+userPassword)
214: int space = -1;
215: for (int i = 0; i < password.length; i++) {
216: if (password[i] == ' ') {
217: space = i;
218: break;
219: }
220: }
221: if (space < 0) {
222: throw Message
223: .getSQLException(ErrorCode.WRONG_PASSWORD_FORMAT);
224: }
225: char[] np = new char[password.length - space - 1];
226: char[] filePassword = new char[space];
227: System.arraycopy(password, space + 1, np, 0, np.length);
228: System.arraycopy(password, 0, filePassword, 0, space);
229: Arrays.fill(password, (char) 0);
230: password = np;
231: filePasswordHash = sha.getKeyPasswordHash("file",
232: filePassword);
233: }
234: userPasswordHash = sha.getKeyPasswordHash(user, password);
235: }
236:
237: public boolean removeProperty(String key, boolean defaultValue) {
238: String x = removeProperty(key, null);
239: return x == null ? defaultValue : Boolean.valueOf(x)
240: .booleanValue();
241: }
242:
243: public String removeProperty(String key, String defaultValue) {
244: if (SysProperties.CHECK && !KNOWN_SETTINGS.contains(key)) {
245: throw Message.getInternalError(key);
246: }
247: Object x = prop.remove(key);
248: return x == null ? defaultValue : x.toString();
249: }
250:
251: public String getName() throws SQLException {
252: if (persistent) {
253: String n = FileUtils.normalize(name
254: + Constants.SUFFIX_DATA_FILE);
255: n = n.substring(0, n.length()
256: - Constants.SUFFIX_DATA_FILE.length());
257: return FileUtils.normalize(n);
258: }
259: return name;
260: }
261:
262: public byte[] getFilePasswordHash() {
263: return filePasswordHash;
264: }
265:
266: public String getUserName() {
267: return user;
268: }
269:
270: public byte[] getUserPasswordHash() {
271: return userPasswordHash;
272: }
273:
274: public String[] getKeys() {
275: String[] keys = new String[prop.size()];
276: prop.keySet().toArray(keys);
277: return keys;
278: }
279:
280: public String getProperty(String key) {
281: Object value = prop.get(key);
282: if (value == null || !(value instanceof String)) {
283: return null;
284: }
285: return value.toString();
286: }
287:
288: public String getProperty(String key, String defaultValue) {
289: if (SysProperties.CHECK && !KNOWN_SETTINGS.contains(key)) {
290: throw Message.getInternalError(key);
291: }
292: String s = getProperty(key);
293: return s == null ? defaultValue : s;
294: }
295:
296: public String getProperty(int setting, String defaultValue) {
297: String key = SetTypes.getTypeName(setting);
298: String s = getProperty(key);
299: return s == null ? defaultValue : s;
300: }
301:
302: public int getIntProperty(int setting, int defaultValue) {
303: String key = SetTypes.getTypeName(setting);
304: String s = getProperty(key, null);
305: try {
306: return s == null ? defaultValue : MathUtils.decodeInt(s);
307: } catch (NumberFormatException e) {
308: return defaultValue;
309: }
310: }
311:
312: public boolean isSSL() {
313: return ssl;
314: }
315:
316: public void setUserName(String name) {
317: // TODO document: the user name is case-insensitive (stored uppercase)
318: // and english conversion is used
319: this .user = StringUtils.toUpperEnglish(name);
320: }
321:
322: public void setUserPasswordHash(byte[] bs) {
323: this .userPasswordHash = bs;
324: }
325:
326: public void setFilePasswordHash(byte[] bs) {
327: this .filePasswordHash = bs;
328: }
329:
330: public void setProperty(String key, String value) {
331: // value is null if the value is an object
332: if (value != null) {
333: prop.setProperty(key, value);
334: }
335: }
336:
337: public String getURL() {
338: return url;
339: }
340:
341: public String getOriginalURL() {
342: return originalURL;
343: }
344:
345: public void setOriginalURL(String url) {
346: originalURL = url;
347: }
348:
349: boolean getTextStorage() throws SQLException {
350: String storage = removeProperty("STORAGE", "BINARY");
351: if ("BINARY".equalsIgnoreCase(storage)) {
352: return false;
353: } else if ("TEXT".equalsIgnoreCase(storage)) {
354: return true;
355: } else {
356: throw Message.getInvalidValueException(storage, "storage");
357: }
358: }
359:
360: public SQLException getFormatException() {
361: String format = Constants.URL_FORMAT;
362: return Message.getSQLException(ErrorCode.URL_FORMAT_ERROR_2,
363: new String[] { format, url });
364: }
365: }
|