/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.engine;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;

import org.h2.api.ErrorCode;
import org.h2.command.dml.SetTypes;
import org.h2.message.DbException;
import org.h2.security.SHA256;
import org.h2.store.fs.FilePathEncrypt;
import org.h2.store.fs.FilePathRec;
import org.h2.store.fs.FileUtils;
import org.h2.util.SortedProperties;
import org.h2.util.StringUtils;
import org.h2.util.Utils;

Encapsulates the connection settings, including user name and password.
/** * Encapsulates the connection settings, including user name and password. */
public class ConnectionInfo implements Cloneable { private static final HashSet<String> KNOWN_SETTINGS; private Properties prop = new Properties(); private String originalURL; private String url; private String user; private byte[] filePasswordHash; private byte[] fileEncryptionKey; private byte[] userPasswordHash;
The database name
/** * The database name */
private String name; private String nameNormalized; private boolean remote; private boolean ssl; private boolean persistent; private boolean unnamed;
Create a connection info object.
Params:
  • name – the database name (including tags), but without the "jdbc:h2:" prefix
/** * Create a connection info object. * * @param name the database name (including tags), but without the * "jdbc:h2:" prefix */
public ConnectionInfo(String name) { this.name = name; this.url = Constants.START_URL + name; parseName(); }
Create a connection info object.
Params:
  • u – the database URL (must start with jdbc:h2:)
  • info – the connection properties
/** * Create a connection info object. * * @param u the database URL (must start with jdbc:h2:) * @param info the connection properties */
public ConnectionInfo(String u, Properties info) { u = remapURL(u); this.originalURL = u; if (!u.startsWith(Constants.START_URL)) { throw DbException.getInvalidValueException("url", u); } this.url = u; readProperties(info); readSettingsFromURL(); setUserName(removeProperty("USER", "")); name = url.substring(Constants.START_URL.length()); parseName(); convertPasswords(); String recoverTest = removeProperty("RECOVER_TEST", null); if (recoverTest != null) { FilePathRec.register(); try { Utils.callStaticMethod("org.h2.store.RecoverTester.init", recoverTest); } catch (Exception e) { throw DbException.convert(e); } name = "rec:" + name; } } static { String[] connectionTime = { "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER", "CREATE", "CACHE_TYPE", "FILE_LOCK", "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS", "INIT", "MVCC", "PASSWORD", "RECOVER", "RECOVER_TEST", "USER", "AUTO_SERVER", "AUTO_SERVER_PORT", "NO_UPGRADE", "AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH", "JMX", "SCOPE_GENERATED_KEYS", "AUTHREALM", "AUTHZPWD" }; HashSet<String> set = new HashSet<>(128); set.addAll(SetTypes.getTypes()); for (String key : connectionTime) { if (!set.add(key)) { DbException.throwInternalError(key); } } KNOWN_SETTINGS = set; } private static boolean isKnownSetting(String s) { return KNOWN_SETTINGS.contains(s); } @Override public ConnectionInfo clone() throws CloneNotSupportedException { ConnectionInfo clone = (ConnectionInfo) super.clone(); clone.prop = (Properties) prop.clone(); clone.filePasswordHash = Utils.cloneByteArray(filePasswordHash); clone.fileEncryptionKey = Utils.cloneByteArray(fileEncryptionKey); clone.userPasswordHash = Utils.cloneByteArray(userPasswordHash); return clone; } private void parseName() { if (".".equals(name)) { name = "mem:"; } if (name.startsWith("tcp:")) { remote = true; name = name.substring("tcp:".length()); } else if (name.startsWith("ssl:")) { remote = true; ssl = true; name = name.substring("ssl:".length()); } else if (name.startsWith("mem:")) { persistent = false; if ("mem:".equals(name)) { unnamed = true; } } else if (name.startsWith("file:")) { name = name.substring("file:".length()); persistent = true; } else { persistent = true; } if (persistent && !remote) { if ("/".equals(SysProperties.FILE_SEPARATOR)) { name = name.replace('\\', '/'); } else { name = name.replace('/', '\\'); } } }
Set the base directory of persistent databases, unless the database is in the user home folder (~).
Params:
  • dir – the new base directory
/** * Set the base directory of persistent databases, unless the database is in * the user home folder (~). * * @param dir the new base directory */
public void setBaseDir(String dir) { if (persistent) { String absDir = FileUtils.unwrap(FileUtils.toRealPath(dir)); boolean absolute = FileUtils.isAbsolute(name); String n; String prefix = null; if (dir.endsWith(SysProperties.FILE_SEPARATOR)) { dir = dir.substring(0, dir.length() - 1); } if (absolute) { n = name; } else { n = FileUtils.unwrap(name); prefix = name.substring(0, name.length() - n.length()); n = dir + SysProperties.FILE_SEPARATOR + n; } String normalizedName = FileUtils.unwrap(FileUtils.toRealPath(n)); if (normalizedName.equals(absDir) || !normalizedName.startsWith(absDir)) { // database name matches the baseDir or // database name is clearly outside of the baseDir throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " + absDir); } if (absDir.endsWith("/") || absDir.endsWith("\\")) { // no further checks are needed for C:/ and similar } else if (normalizedName.charAt(absDir.length()) != '/') { // database must be within the directory // (with baseDir=/test, the database name must not be // /test2/x and not /test2) throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " + absDir); } if (!absolute) { name = prefix + dir + SysProperties.FILE_SEPARATOR + FileUtils.unwrap(name); } } }
Check if this is a remote connection.
Returns:true if it is
/** * Check if this is a remote connection. * * @return true if it is */
public boolean isRemote() { return remote; }
Check if the referenced database is persistent.
Returns:true if it is
/** * Check if the referenced database is persistent. * * @return true if it is */
public boolean isPersistent() { return persistent; }
Check if the referenced database is an unnamed in-memory database.
Returns:true if it is
/** * Check if the referenced database is an unnamed in-memory database. * * @return true if it is */
boolean isUnnamedInMemory() { return unnamed; } private void readProperties(Properties info) { Object[] list = info.keySet().toArray(); DbSettings s = null; for (Object k : list) { String key = StringUtils.toUpperEnglish(k.toString()); if (prop.containsKey(key)) { throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key); } Object value = info.get(k); if (isKnownSetting(key)) { prop.put(key, value); } else { if (s == null) { s = getDbSettings(); } if (s.containsKey(key)) { prop.put(key, value); } } } } private void readSettingsFromURL() { DbSettings defaultSettings = DbSettings.getDefaultSettings(); int idx = url.indexOf(';'); if (idx >= 0) { String settings = url.substring(idx + 1); url = url.substring(0, idx); String[] list = StringUtils.arraySplit(settings, ';', false); for (String setting : list) { if (setting.isEmpty()) { continue; } int equal = setting.indexOf('='); if (equal < 0) { throw getFormatException(); } String value = setting.substring(equal + 1); String key = setting.substring(0, equal); key = StringUtils.toUpperEnglish(key); if (!isKnownSetting(key) && !defaultSettings.containsKey(key)) { throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_1, key); } String old = prop.getProperty(key); if (old != null && !old.equals(value)) { throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key); } prop.setProperty(key, value); } } } private void preservePasswordForAuthentication(Object password) { if ((!isRemote() || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) { prop.put("AUTHZPWD",password instanceof char[] ? new String((char[])password) : password); } } private char[] removePassword() { Object p = prop.remove("PASSWORD"); preservePasswordForAuthentication(p); if (p == null) { return new char[0]; } else if (p instanceof char[]) { return (char[]) p; } else { return p.toString().toCharArray(); } }
Split the password property into file password and user password if necessary, and convert them to the internal hash format.
/** * Split the password property into file password and user password if * necessary, and convert them to the internal hash format. */
private void convertPasswords() { char[] password = removePassword(); boolean passwordHash = removeProperty("PASSWORD_HASH", false); if (getProperty("CIPHER", null) != null) { // split password into (filePassword+' '+userPassword) int space = -1; for (int i = 0, len = password.length; i < len; i++) { if (password[i] == ' ') { space = i; break; } } if (space < 0) { throw DbException.get(ErrorCode.WRONG_PASSWORD_FORMAT); } char[] np = Arrays.copyOfRange(password, space + 1, password.length); char[] filePassword = Arrays.copyOf(password, space); Arrays.fill(password, (char) 0); password = np; fileEncryptionKey = FilePathEncrypt.getPasswordBytes(filePassword); filePasswordHash = hashPassword(passwordHash, "file", filePassword); } userPasswordHash = hashPassword(passwordHash, user, password); } private static byte[] hashPassword(boolean passwordHash, String userName, char[] password) { if (passwordHash) { return StringUtils.convertHexToBytes(new String(password)); } if (userName.isEmpty() && password.length == 0) { return new byte[0]; } return SHA256.getKeyPasswordHash(userName, password); }
Get a boolean property if it is set and return the value.
Params:
  • key – the property name
  • defaultValue – the default value
Returns:the value
/** * Get a boolean property if it is set and return the value. * * @param key the property name * @param defaultValue the default value * @return the value */
public boolean getProperty(String key, boolean defaultValue) { return Utils.parseBoolean(getProperty(key, null), defaultValue, false); }
Remove a boolean property if it is set and return the value.
Params:
  • key – the property name
  • defaultValue – the default value
Returns:the value
/** * Remove a boolean property if it is set and return the value. * * @param key the property name * @param defaultValue the default value * @return the value */
public boolean removeProperty(String key, boolean defaultValue) { return Utils.parseBoolean(removeProperty(key, null), defaultValue, false); }
Remove a String property if it is set and return the value.
Params:
  • key – the property name
  • defaultValue – the default value
Returns:the value
/** * Remove a String property if it is set and return the value. * * @param key the property name * @param defaultValue the default value * @return the value */
String removeProperty(String key, String defaultValue) { if (SysProperties.CHECK && !isKnownSetting(key)) { DbException.throwInternalError(key); } Object x = prop.remove(key); return x == null ? defaultValue : x.toString(); }
Get the unique and normalized database name (excluding settings).
Returns:the database name
/** * Get the unique and normalized database name (excluding settings). * * @return the database name */
public String getName() { if (!persistent) { return name; } if (nameNormalized == null) { if (!SysProperties.IMPLICIT_RELATIVE_PATH) { if (!FileUtils.isAbsolute(name)) { if (!name.contains("./") && !name.contains(".\\") && !name.contains(":/") && !name.contains(":\\")) { // the name could start with "./", or // it could start with a prefix such as "nio:./" // for Windows, the path "\test" is not considered // absolute as the drive letter is missing, // but we consider it absolute throw DbException.get( ErrorCode.URL_RELATIVE_TO_CWD, originalURL); } } } String suffix = Constants.SUFFIX_PAGE_FILE; String n; if (FileUtils.exists(name + suffix)) { n = FileUtils.toRealPath(name + suffix); } else { suffix = Constants.SUFFIX_MV_FILE; n = FileUtils.toRealPath(name + suffix); } String fileName = FileUtils.getName(n); if (fileName.length() < suffix.length() + 1) { throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name); } nameNormalized = n.substring(0, n.length() - suffix.length()); } return nameNormalized; }
Get the file password hash if it is set.
Returns:the password hash or null
/** * Get the file password hash if it is set. * * @return the password hash or null */
public byte[] getFilePasswordHash() { return filePasswordHash; } byte[] getFileEncryptionKey() { return fileEncryptionKey; }
Get the name of the user.
Returns:the user name
/** * Get the name of the user. * * @return the user name */
public String getUserName() { return user; }
Get the user password hash.
Returns:the password hash
/** * Get the user password hash. * * @return the password hash */
byte[] getUserPasswordHash() { return userPasswordHash; }
Get the property keys.
Returns:the property keys
/** * Get the property keys. * * @return the property keys */
String[] getKeys() { return prop.keySet().toArray(new String[prop.size()]); }
Get the value of the given property.
Params:
  • key – the property key
Returns:the value as a String
/** * Get the value of the given property. * * @param key the property key * @return the value as a String */
String getProperty(String key) { Object value = prop.get(key); if (!(value instanceof String)) { return null; } return value.toString(); }
Get the value of the given property.
Params:
  • key – the property key
  • defaultValue – the default value
Returns:the value as a String
/** * Get the value of the given property. * * @param key the property key * @param defaultValue the default value * @return the value as a String */
int getProperty(String key, int defaultValue) { if (SysProperties.CHECK && !isKnownSetting(key)) { DbException.throwInternalError(key); } String s = getProperty(key); return s == null ? defaultValue : Integer.parseInt(s); }
Get the value of the given property.
Params:
  • key – the property key
  • defaultValue – the default value
Returns:the value as a String
/** * Get the value of the given property. * * @param key the property key * @param defaultValue the default value * @return the value as a String */
public String getProperty(String key, String defaultValue) { if (SysProperties.CHECK && !isKnownSetting(key)) { DbException.throwInternalError(key); } String s = getProperty(key); return s == null ? defaultValue : s; }
Get the value of the given property.
Params:
  • setting – the setting id
  • defaultValue – the default value
Returns:the value as a String
/** * Get the value of the given property. * * @param setting the setting id * @param defaultValue the default value * @return the value as a String */
String getProperty(int setting, String defaultValue) { String key = SetTypes.getTypeName(setting); String s = getProperty(key); return s == null ? defaultValue : s; }
Get the value of the given property.
Params:
  • setting – the setting id
  • defaultValue – the default value
Returns:the value as an integer
/** * Get the value of the given property. * * @param setting the setting id * @param defaultValue the default value * @return the value as an integer */
int getIntProperty(int setting, int defaultValue) { String key = SetTypes.getTypeName(setting); String s = getProperty(key, null); try { return s == null ? defaultValue : Integer.decode(s); } catch (NumberFormatException e) { return defaultValue; } }
Check if this is a remote connection with SSL enabled.
Returns:true if it is
/** * Check if this is a remote connection with SSL enabled. * * @return true if it is */
boolean isSSL() { return ssl; }
Overwrite the user name. The user name is case-insensitive and stored in uppercase. English conversion is used.
Params:
  • name – the user name
/** * Overwrite the user name. The user name is case-insensitive and stored in * uppercase. English conversion is used. * * @param name the user name */
public void setUserName(String name) { this.user = StringUtils.toUpperEnglish(name); }
Set the user password hash.
Params:
  • hash – the new hash value
/** * Set the user password hash. * * @param hash the new hash value */
public void setUserPasswordHash(byte[] hash) { this.userPasswordHash = hash; }
Set the file password hash.
Params:
  • hash – the new hash value
/** * Set the file password hash. * * @param hash the new hash value */
public void setFilePasswordHash(byte[] hash) { this.filePasswordHash = hash; } public void setFileEncryptionKey(byte[] key) { this.fileEncryptionKey = key; }
Overwrite a property.
Params:
  • key – the property name
  • value – the value
/** * Overwrite a property. * * @param key the property name * @param value the value */
public void setProperty(String key, String value) { // value is null if the value is an object if (value != null) { prop.setProperty(key, value); } }
Get the database URL.
Returns:the URL
/** * Get the database URL. * * @return the URL */
public String getURL() { return url; }
Get the complete original database URL.
Returns:the database URL
/** * Get the complete original database URL. * * @return the database URL */
public String getOriginalURL() { return originalURL; }
Set the original database URL.
Params:
  • url – the database url
/** * Set the original database URL. * * @param url the database url */
public void setOriginalURL(String url) { originalURL = url; }
Generate an URL format exception.
Returns:the exception
/** * Generate an URL format exception. * * @return the exception */
DbException getFormatException() { String format = Constants.URL_FORMAT; return DbException.get(ErrorCode.URL_FORMAT_ERROR_2, format, url); }
Switch to server mode, and set the server name and database key.
Params:
  • serverKey – the server name, '/', and the security key
/** * Switch to server mode, and set the server name and database key. * * @param serverKey the server name, '/', and the security key */
public void setServerKey(String serverKey) { remote = true; persistent = false; this.name = serverKey; } public DbSettings getDbSettings() { DbSettings defaultSettings = DbSettings.getDefaultSettings(); HashMap<String, String> s = new HashMap<>(); for (Object k : prop.keySet()) { String key = k.toString(); if (!isKnownSetting(key) && defaultSettings.containsKey(key)) { s.put(key, prop.getProperty(key)); } } return DbSettings.getInstance(s); } private static String remapURL(String url) { String urlMap = SysProperties.URL_MAP; if (urlMap != null && !urlMap.isEmpty()) { try { SortedProperties prop; prop = SortedProperties.loadProperties(urlMap); String url2 = prop.getProperty(url); if (url2 == null) { prop.put(url, ""); prop.store(urlMap); } else { url2 = url2.trim(); if (!url2.isEmpty()) { return url2; } } } catch (IOException e) { throw DbException.convert(e); } } return url; }
Clear authentication properties.
/** * Clear authentication properties. */
public void cleanAuthenticationInfo() { removeProperty("AUTHREALM", false); removeProperty("AUTHZPWD", false); } }