EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.engine]

COVERAGE SUMMARY FOR SOURCE FILE [ConnectionInfo.java]

nameclass, %method, %block, %line, %
ConnectionInfo.java100% (1/1)98%  (41/42)88%  (1071/1224)85%  (218.9/257)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ConnectionInfo100% (1/1)98%  (41/42)88%  (1071/1224)85%  (218.9/257)
getFormatException (): DbException 0%   (0/1)0%   (0/16)0%   (0/2)
remapURL (String): String 100% (1/1)15%  (6/40)18%  (2.6/14)
ConnectionInfo (String, Properties): void 100% (1/1)62%  (50/80)68%  (15/22)
parseName (): void 100% (1/1)79%  (83/105)82%  (18/22)
getProperty (String, String): String 100% (1/1)83%  (15/18)75%  (3/4)
getIntProperty (int, int): int 100% (1/1)84%  (16/19)60%  (3/5)
getProperty (String, int): int 100% (1/1)84%  (16/19)75%  (3/4)
removeProperty (String, String): String 100% (1/1)85%  (17/20)75%  (3/4)
readSettingsFromURL (): void 100% (1/1)86%  (89/104)85%  (18.6/22)
setBaseDir (String): void 100% (1/1)90%  (116/129)89%  (17.9/20)
readProperties (Properties): void 100% (1/1)94%  (66/70)93%  (14/15)
getProperty (String, boolean): boolean 100% (1/1)96%  (27/28)98%  (5.8/6)
convertPasswords (): void 100% (1/1)97%  (84/87)95%  (19/20)
<static initializer> 100% (1/1)98%  (124/127)90%  (9/10)
ConnectionInfo (String): void 100% (1/1)100% (23/23)100% (6/6)
clone (): ConnectionInfo 100% (1/1)100% (27/27)100% (6/6)
getDbSettings (): DbSettings 100% (1/1)100% (37/37)100% (8/8)
getFileEncryptionKey (): byte [] 100% (1/1)100% (3/3)100% (1/1)
getFilePasswordHash (): byte [] 100% (1/1)100% (3/3)100% (1/1)
getKeys (): String [] 100% (1/1)100% (13/13)100% (3/3)
getName (): String 100% (1/1)100% (106/106)100% (17/17)
getOriginalURL (): String 100% (1/1)100% (3/3)100% (1/1)
getProperty (String): String 100% (1/1)100% (15/15)100% (4/4)
getProperty (int, String): String 100% (1/1)100% (13/13)100% (3/3)
getURL (): String 100% (1/1)100% (3/3)100% (1/1)
getUserName (): String 100% (1/1)100% (3/3)100% (1/1)
getUserPasswordHash (): byte [] 100% (1/1)100% (3/3)100% (1/1)
hashPassword (boolean, String, char []): byte [] 100% (1/1)100% (21/21)100% (5/5)
isKnownSetting (String): boolean 100% (1/1)100% (4/4)100% (1/1)
isPersistent (): boolean 100% (1/1)100% (3/3)100% (1/1)
isRemote (): boolean 100% (1/1)100% (3/3)100% (1/1)
isSSL (): boolean 100% (1/1)100% (3/3)100% (1/1)
isUnnamedInMemory (): boolean 100% (1/1)100% (3/3)100% (1/1)
removePassword (): char [] 100% (1/1)100% (21/21)100% (6/6)
removeProperty (String, boolean): boolean 100% (1/1)100% (12/12)100% (2/2)
setFileEncryptionKey (byte []): void 100% (1/1)100% (4/4)100% (2/2)
setFilePasswordHash (byte []): void 100% (1/1)100% (4/4)100% (2/2)
setOriginalURL (String): void 100% (1/1)100% (4/4)100% (2/2)
setProperty (String, String): void 100% (1/1)100% (9/9)100% (3/3)
setServerKey (String): void 100% (1/1)100% (10/10)100% (4/4)
setUserName (String): void 100% (1/1)100% (5/5)100% (2/2)
setUserPasswordHash (byte []): void 100% (1/1)100% (4/4)100% (2/2)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.engine;
7 
8import java.io.IOException;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.Properties;
14 
15import org.h2.api.ErrorCode;
16import org.h2.command.dml.SetTypes;
17import org.h2.message.DbException;
18import org.h2.security.SHA256;
19import org.h2.store.fs.FilePathEncrypt;
20import org.h2.store.fs.FilePathRec;
21import org.h2.store.fs.FileUtils;
22import org.h2.util.New;
23import org.h2.util.SortedProperties;
24import org.h2.util.StringUtils;
25import org.h2.util.Utils;
26 
27/**
28 * Encapsulates the connection settings, including user name and password.
29 */
30public class ConnectionInfo implements Cloneable {
31    private static final HashSet<String> KNOWN_SETTINGS = New.hashSet();
32 
33    private Properties prop = new Properties();
34    private String originalURL;
35    private String url;
36    private String user;
37    private byte[] filePasswordHash;
38    private byte[] fileEncryptionKey;
39    private byte[] userPasswordHash;
40 
41    /**
42     * The database name
43     */
44    private String name;
45    private String nameNormalized;
46    private boolean remote;
47    private boolean ssl;
48    private boolean persistent;
49    private boolean unnamed;
50 
51    /**
52     * Create a connection info object.
53     *
54     * @param name the database name (including tags), but without the
55     *            "jdbc:h2:" prefix
56     */
57    public ConnectionInfo(String name) {
58        this.name = name;
59        this.url = Constants.START_URL + name;
60        parseName();
61    }
62 
63    /**
64     * Create a connection info object.
65     *
66     * @param u the database URL (must start with jdbc:h2:)
67     * @param info the connection properties
68     */
69    public ConnectionInfo(String u, Properties info) {
70        u = remapURL(u);
71        this.originalURL = u;
72        if (!u.startsWith(Constants.START_URL)) {
73            throw DbException.getInvalidValueException("url", u);
74        }
75        this.url = u;
76        readProperties(info);
77        readSettingsFromURL();
78        setUserName(removeProperty("USER", ""));
79        convertPasswords();
80        name = url.substring(Constants.START_URL.length());
81        parseName();
82        String recoverTest = removeProperty("RECOVER_TEST", null);
83        if (recoverTest != null) {
84            FilePathRec.register();
85            try {
86                Utils.callStaticMethod("org.h2.store.RecoverTester.init", recoverTest);
87            } catch (Exception e) {
88                throw DbException.convert(e);
89            }
90            name = "rec:" + name;
91        }
92    }
93 
94    static {
95        ArrayList<String> list = SetTypes.getTypes();
96        HashSet<String> set = KNOWN_SETTINGS;
97        set.addAll(list);
98        String[] connectionTime = { "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER",
99                "CREATE", "CACHE_TYPE", "FILE_LOCK", "IGNORE_UNKNOWN_SETTINGS",
100                "IFEXISTS", "INIT", "PASSWORD", "RECOVER", "RECOVER_TEST",
101                "USER", "AUTO_SERVER", "AUTO_SERVER_PORT", "NO_UPGRADE",
102                "AUTO_RECONNECT", "OPEN_NEW", "PAGE_SIZE", "PASSWORD_HASH", "JMX" };
103        for (String key : connectionTime) {
104            if (SysProperties.CHECK && set.contains(key)) {
105                DbException.throwInternalError(key);
106            }
107            set.add(key);
108        }
109    }
110 
111    private static boolean isKnownSetting(String s) {
112        return KNOWN_SETTINGS.contains(s);
113    }
114 
115    @Override
116    public ConnectionInfo clone() throws CloneNotSupportedException {
117        ConnectionInfo clone = (ConnectionInfo) super.clone();
118        clone.prop = (Properties) prop.clone();
119        clone.filePasswordHash = Utils.cloneByteArray(filePasswordHash);
120        clone.fileEncryptionKey = Utils.cloneByteArray(fileEncryptionKey);
121        clone.userPasswordHash = Utils.cloneByteArray(userPasswordHash);
122        return clone;
123    }
124 
125    private void parseName() {
126        if (".".equals(name)) {
127            name = "mem:";
128        }
129        if (name.startsWith("tcp:")) {
130            remote = true;
131            name = name.substring("tcp:".length());
132        } else if (name.startsWith("ssl:")) {
133            remote = true;
134            ssl = true;
135            name = name.substring("ssl:".length());
136        } else if (name.startsWith("mem:")) {
137            persistent = false;
138            if ("mem:".equals(name)) {
139                unnamed = true;
140            }
141        } else if (name.startsWith("file:")) {
142            name = name.substring("file:".length());
143            persistent = true;
144        } else {
145            persistent = true;
146        }
147        if (persistent && !remote) {
148            if ("/".equals(SysProperties.FILE_SEPARATOR)) {
149                name = name.replace('\\', '/');
150            } else {
151                name = name.replace('/', '\\');
152            }
153        }
154    }
155 
156    /**
157     * Set the base directory of persistent databases, unless the database is in
158     * the user home folder (~).
159     *
160     * @param dir the new base directory
161     */
162    public void setBaseDir(String dir) {
163        if (persistent) {
164            String absDir = FileUtils.unwrap(FileUtils.toRealPath(dir));
165            boolean absolute = FileUtils.isAbsolute(name);
166            String n;
167            String prefix = null;
168            if (dir.endsWith(SysProperties.FILE_SEPARATOR)) {
169                dir = dir.substring(0, dir.length() - 1);
170            }
171            if (absolute) {
172                n = name;
173            } else {
174                n  = FileUtils.unwrap(name);
175                prefix = name.substring(0, name.length() - n.length());
176                n = dir + SysProperties.FILE_SEPARATOR + n;
177            }
178            String normalizedName = FileUtils.unwrap(FileUtils.toRealPath(n));
179            if (normalizedName.equals(absDir) || !normalizedName.startsWith(absDir)) {
180                // database name matches the baseDir or
181                // database name is clearly outside of the baseDir
182                throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " +
183                        absDir);
184            }
185            if (absDir.endsWith("/") || absDir.endsWith("\\")) {
186                // no further checks are needed for C:/ and similar
187            } else if (normalizedName.charAt(absDir.length()) != '/') {
188                // database must be within the directory
189                // (with baseDir=/test, the database name must not be
190                // /test2/x and not /test2)
191                throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " +
192                        absDir);
193            }
194            if (!absolute) {
195                name = prefix + dir + SysProperties.FILE_SEPARATOR + FileUtils.unwrap(name);
196            }
197        }
198    }
199 
200    /**
201     * Check if this is a remote connection.
202     *
203     * @return true if it is
204     */
205    public boolean isRemote() {
206        return remote;
207    }
208 
209    /**
210     * Check if the referenced database is persistent.
211     *
212     * @return true if it is
213     */
214    public boolean isPersistent() {
215        return persistent;
216    }
217 
218    /**
219     * Check if the referenced database is an unnamed in-memory database.
220     *
221     * @return true if it is
222     */
223    boolean isUnnamedInMemory() {
224        return unnamed;
225    }
226 
227    private void readProperties(Properties info) {
228        Object[] list = new Object[info.size()];
229        info.keySet().toArray(list);
230        DbSettings s = null;
231        for (Object k : list) {
232            String key = StringUtils.toUpperEnglish(k.toString());
233            if (prop.containsKey(key)) {
234                throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key);
235            }
236            Object value = info.get(k);
237            if (isKnownSetting(key)) {
238                prop.put(key, value);
239            } else {
240                if (s == null) {
241                    s = getDbSettings();
242                }
243                if (s.containsKey(key)) {
244                    prop.put(key, value);
245                }
246            }
247        }
248    }
249 
250    private void readSettingsFromURL() {
251        DbSettings defaultSettings = DbSettings.getDefaultSettings();
252        int idx = url.indexOf(';');
253        if (idx >= 0) {
254            String settings = url.substring(idx + 1);
255            url = url.substring(0, idx);
256            String[] list = StringUtils.arraySplit(settings, ';', false);
257            for (String setting : list) {
258                if (setting.length() == 0) {
259                    continue;
260                }
261                int equal = setting.indexOf('=');
262                if (equal < 0) {
263                    throw getFormatException();
264                }
265                String value = setting.substring(equal + 1);
266                String key = setting.substring(0, equal);
267                key = StringUtils.toUpperEnglish(key);
268                if (!isKnownSetting(key) && !defaultSettings.containsKey(key)) {
269                    throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_1, key);
270                }
271                String old = prop.getProperty(key);
272                if (old != null && !old.equals(value)) {
273                    throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key);
274                }
275                prop.setProperty(key, value);
276            }
277        }
278    }
279 
280    private char[] removePassword() {
281        Object p = prop.remove("PASSWORD");
282        if (p == null) {
283            return new char[0];
284        } else if (p instanceof char[]) {
285            return (char[]) p;
286        } else {
287            return p.toString().toCharArray();
288        }
289    }
290 
291    /**
292     * Split the password property into file password and user password if
293     * necessary, and convert them to the internal hash format.
294     */
295    private void convertPasswords() {
296        char[] password = removePassword();
297        boolean passwordHash = removeProperty("PASSWORD_HASH", false);
298        if (getProperty("CIPHER", null) != null) {
299            // split password into (filePassword+' '+userPassword)
300            int space = -1;
301            for (int i = 0, len = password.length; i < len; i++) {
302                if (password[i] == ' ') {
303                    space = i;
304                    break;
305                }
306            }
307            if (space < 0) {
308                throw DbException.get(ErrorCode.WRONG_PASSWORD_FORMAT);
309            }
310            char[] np = new char[password.length - space - 1];
311            char[] filePassword = new char[space];
312            System.arraycopy(password, space + 1, np, 0, np.length);
313            System.arraycopy(password, 0, filePassword, 0, space);
314            Arrays.fill(password, (char) 0);
315            password = np;
316            fileEncryptionKey = FilePathEncrypt.getPasswordBytes(filePassword);
317            filePasswordHash = hashPassword(passwordHash, "file", filePassword);
318        }
319        userPasswordHash = hashPassword(passwordHash, user, password);
320    }
321 
322    private static byte[] hashPassword(boolean passwordHash, String userName,
323            char[] password) {
324        if (passwordHash) {
325            return StringUtils.convertHexToBytes(new String(password));
326        }
327        if (userName.length() == 0 && password.length == 0) {
328            return new byte[0];
329        }
330        return SHA256.getKeyPasswordHash(userName, password);
331    }
332 
333    /**
334     * Get a boolean property if it is set and return the value.
335     *
336     * @param key the property name
337     * @param defaultValue the default value
338     * @return the value
339     */
340    boolean getProperty(String key, boolean defaultValue) {
341        String x = getProperty(key, null);
342        if (x == null) {
343            return defaultValue;
344        }
345        // support 0 / 1 (like the parser)
346        if (x.length() == 1 && Character.isDigit(x.charAt(0))) {
347            return Integer.parseInt(x) != 0;
348        }
349        return Boolean.parseBoolean(x);
350    }
351 
352    /**
353     * Remove a boolean property if it is set and return the value.
354     *
355     * @param key the property name
356     * @param defaultValue the default value
357     * @return the value
358     */
359    public boolean removeProperty(String key, boolean defaultValue) {
360        String x = removeProperty(key, null);
361        return x == null ? defaultValue : Boolean.parseBoolean(x);
362    }
363 
364    /**
365     * Remove a String property if it is set and return the value.
366     *
367     * @param key the property name
368     * @param defaultValue the default value
369     * @return the value
370     */
371    String removeProperty(String key, String defaultValue) {
372        if (SysProperties.CHECK && !isKnownSetting(key)) {
373            DbException.throwInternalError(key);
374        }
375        Object x = prop.remove(key);
376        return x == null ? defaultValue : x.toString();
377    }
378 
379    /**
380     * Get the unique and normalized database name (excluding settings).
381     *
382     * @return the database name
383     */
384    public String getName() {
385        if (persistent) {
386            if (nameNormalized == null) {
387                if (!SysProperties.IMPLICIT_RELATIVE_PATH) {
388                    if (!FileUtils.isAbsolute(name)) {
389                        if (name.indexOf("./") < 0 &&
390                                name.indexOf(".\\") < 0 &&
391                                name.indexOf(":/") < 0 &&
392                                name.indexOf(":\\") < 0) {
393                            // the name could start with "./", or
394                            // it could start with a prefix such as "nio:./"
395                            // for Windows, the path "\test" is not considered
396                            // absolute as the drive letter is missing,
397                            // but we consider it absolute
398                            throw DbException.get(
399                                    ErrorCode.URL_RELATIVE_TO_CWD,
400                                    originalURL);
401                        }
402                    }
403                }
404                String suffix = Constants.SUFFIX_PAGE_FILE;
405                String n;
406                if (FileUtils.exists(name + suffix)) {
407                    n = FileUtils.toRealPath(name + suffix);
408                } else {
409                    suffix = Constants.SUFFIX_MV_FILE;
410                    n = FileUtils.toRealPath(name + suffix);
411                }
412                String fileName = FileUtils.getName(n);
413                if (fileName.length() < suffix.length() + 1) {
414                    throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name);
415                }
416                nameNormalized = n.substring(0, n.length() - suffix.length());
417            }
418            return nameNormalized;
419        }
420        return name;
421    }
422 
423    /**
424     * Get the file password hash if it is set.
425     *
426     * @return the password hash or null
427     */
428    public byte[] getFilePasswordHash() {
429        return filePasswordHash;
430    }
431 
432    byte[] getFileEncryptionKey() {
433        return fileEncryptionKey;
434    }
435 
436    /**
437     * Get the name of the user.
438     *
439     * @return the user name
440     */
441    public String getUserName() {
442        return user;
443    }
444 
445    /**
446     * Get the user password hash.
447     *
448     * @return the password hash
449     */
450    byte[] getUserPasswordHash() {
451        return userPasswordHash;
452    }
453 
454    /**
455     * Get the property keys.
456     *
457     * @return the property keys
458     */
459    String[] getKeys() {
460        String[] keys = new String[prop.size()];
461        prop.keySet().toArray(keys);
462        return keys;
463    }
464 
465    /**
466     * Get the value of the given property.
467     *
468     * @param key the property key
469     * @return the value as a String
470     */
471    String getProperty(String key) {
472        Object value = prop.get(key);
473        if (value == null || !(value instanceof String)) {
474            return null;
475        }
476        return value.toString();
477    }
478 
479    /**
480     * Get the value of the given property.
481     *
482     * @param key the property key
483     * @param defaultValue the default value
484     * @return the value as a String
485     */
486    int getProperty(String key, int defaultValue) {
487        if (SysProperties.CHECK && !isKnownSetting(key)) {
488            DbException.throwInternalError(key);
489        }
490        String s = getProperty(key);
491        return s == null ? defaultValue : Integer.parseInt(s);
492    }
493 
494    /**
495     * Get the value of the given property.
496     *
497     * @param key the property key
498     * @param defaultValue the default value
499     * @return the value as a String
500     */
501    public String getProperty(String key, String defaultValue) {
502        if (SysProperties.CHECK && !isKnownSetting(key)) {
503            DbException.throwInternalError(key);
504        }
505        String s = getProperty(key);
506        return s == null ? defaultValue : s;
507    }
508 
509    /**
510     * Get the value of the given property.
511     *
512     * @param setting the setting id
513     * @param defaultValue the default value
514     * @return the value as a String
515     */
516    String getProperty(int setting, String defaultValue) {
517        String key = SetTypes.getTypeName(setting);
518        String s = getProperty(key);
519        return s == null ? defaultValue : s;
520    }
521 
522    /**
523     * Get the value of the given property.
524     *
525     * @param setting the setting id
526     * @param defaultValue the default value
527     * @return the value as an integer
528     */
529    int getIntProperty(int setting, int defaultValue) {
530        String key = SetTypes.getTypeName(setting);
531        String s = getProperty(key, null);
532        try {
533            return s == null ? defaultValue : Integer.decode(s);
534        } catch (NumberFormatException e) {
535            return defaultValue;
536        }
537    }
538 
539    /**
540     * Check if this is a remote connection with SSL enabled.
541     *
542     * @return true if it is
543     */
544    boolean isSSL() {
545        return ssl;
546    }
547 
548    /**
549     * Overwrite the user name. The user name is case-insensitive and stored in
550     * uppercase. English conversion is used.
551     *
552     * @param name the user name
553     */
554    public void setUserName(String name) {
555        this.user = StringUtils.toUpperEnglish(name);
556    }
557 
558    /**
559     * Set the user password hash.
560     *
561     * @param hash the new hash value
562     */
563    public void setUserPasswordHash(byte[] hash) {
564        this.userPasswordHash = hash;
565    }
566 
567    /**
568     * Set the file password hash.
569     *
570     * @param hash the new hash value
571     */
572    public void setFilePasswordHash(byte[] hash) {
573        this.filePasswordHash = hash;
574    }
575 
576    public void setFileEncryptionKey(byte[] key) {
577        this.fileEncryptionKey = key;
578    }
579 
580    /**
581     * Overwrite a property.
582     *
583     * @param key the property name
584     * @param value the value
585     */
586    public void setProperty(String key, String value) {
587        // value is null if the value is an object
588        if (value != null) {
589            prop.setProperty(key, value);
590        }
591    }
592 
593    /**
594     * Get the database URL.
595     *
596     * @return the URL
597     */
598    public String getURL() {
599        return url;
600    }
601 
602    /**
603     * Get the complete original database URL.
604     *
605     * @return the database URL
606     */
607    public String getOriginalURL() {
608        return originalURL;
609    }
610 
611    /**
612     * Set the original database URL.
613     *
614     * @param url the database url
615     */
616    public void setOriginalURL(String url) {
617        originalURL = url;
618    }
619 
620    /**
621     * Generate an URL format exception.
622     *
623     * @return the exception
624     */
625    DbException getFormatException() {
626        String format = Constants.URL_FORMAT;
627        return DbException.get(ErrorCode.URL_FORMAT_ERROR_2, format, url);
628    }
629 
630    /**
631     * Switch to server mode, and set the server name and database key.
632     *
633     * @param serverKey the server name, '/', and the security key
634     */
635    public void setServerKey(String serverKey) {
636        remote = true;
637        persistent = false;
638        this.name = serverKey;
639    }
640 
641    public DbSettings getDbSettings() {
642        DbSettings defaultSettings = DbSettings.getDefaultSettings();
643        HashMap<String, String> s = New.hashMap();
644        for (Object k : prop.keySet()) {
645            String key = k.toString();
646            if (!isKnownSetting(key) && defaultSettings.containsKey(key)) {
647                s.put(key, prop.getProperty(key));
648            }
649        }
650        return DbSettings.getInstance(s);
651    }
652 
653    private static String remapURL(String url) {
654        String urlMap = SysProperties.URL_MAP;
655        if (urlMap != null && urlMap.length() > 0) {
656            try {
657                SortedProperties prop;
658                prop = SortedProperties.loadProperties(urlMap);
659                String url2 = prop.getProperty(url);
660                if (url2 == null) {
661                    prop.put(url, "");
662                    prop.store(urlMap);
663                } else {
664                    url2 = url2.trim();
665                    if (url2.length() > 0) {
666                        return url2;
667                    }
668                }
669            } catch (IOException e) {
670                throw DbException.convert(e);
671            }
672        }
673        return url;
674    }
675 
676}

[all classes][org.h2.engine]
EMMA 2.0.5312 (C) Vladimir Roubtsov