1 /* Copyright (c) 2001-2014, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 
32 package org.hsqldb;
33 
34 import java.util.Locale;
35 
36 import org.hsqldb.persist.HsqlProperties;
37 import org.hsqldb.server.ServerConstants;
38 
39 /*
40  * Parses a connection URL into parts.
41  *
42  * @author Fred Toussi (fredt@users dot sourceforge.net)
43  * @version 2.0.1
44  * @since 1.8.0
45  */
46 
47 // patch 1.9.0 by Blaine Simpson - IPv6 support
48 public class DatabaseURL {
49 
50     public static final String S_DOT               = ".";
51     public static final String S_MEM               = "mem:";
52     public static final String S_FILE              = "file:";
53     public static final String S_RES               = "res:";
54     public static final String S_ALIAS             = "alias:";
55     public static final String S_HSQL              = "hsql://";
56     public static final String S_HSQLS             = "hsqls://";
57     public static final String S_HTTP              = "http://";
58     public static final String S_HTTPS             = "https://";
59     public static final String S_URL_PREFIX        = "jdbc:hsqldb:";
60     public static final String S_URL_INTERNAL      = "jdbc:default:connection";
61     public static final String url_connection_type = "connection_type";
62     public static final String url_database        = "database";
63 
64     /**
65      * Returns true if type represents an in-process connection to a file backed
66      * database.
67      */
68     public static boolean isFileBasedDatabaseType(String type) {
69 
70         if (type == S_FILE || type == S_RES) {
71             return true;
72         }
73 
74         return false;
75     }
76 
77     /**
78      * Returns true if type represents an in-process connection to database.
79      */
80     public static boolean isInProcessDatabaseType(String type) {
81 
82         if (type == S_FILE || type == S_RES || type == S_MEM) {
83             return true;
84         }
85 
86         return false;
87     }
88 
89     /**
90      * Parses the url into components that are returned in a properties object.
91      *
92      * <p> The following components are isolated:
93      *
94      * <p>
95      * <ul> url: the original url
96      *
97      * <p> connection_type: a static string that indicate the protocol. If the
98      * url does not begin with a valid protocol, null is returned by this method
99      * instead of the properties object.
100      *
101      * <p> host: name of host in networked modes in lowercase
102      *
103      * <p> port: port number in networked mode, or 0 if not present
104      *
105      * <p> path: path of the resource on server in networked modes, minimum
106      * (slash) with path elements appended apart from servlet path which is
107      * (slash) plus the name of the servlet
108      *
109      * <p> database: database name. For memory, networked modes,
110      * this is returned in lowercase, for file: and res: databases the original case of
111      * characters is preserved. Returns empty string if name is not present in
112      * the url.
113      *
114      * <p> for each protocol if port number is not in the url
115      *
116      * <p> Additional connection properties specified as key/value pairs.
117      * </ul>
118      *
119      * @return null returned if the part that should represent the port is not
120      *   an integer or the part for database name is empty. Empty
121      *   HsqlProperties returned if if url does not begin with valid protocol
122      *   and could refer to another JDBC driver.
123      * @param url String
124      * @param hasPrefix indicates URL prefix is present
125      * @param noPath indicates empty path and verbatim use of path elements as
126      * database
127      */
128     public static HsqlProperties parseURL(String url, boolean hasPrefix,
129                                           boolean noPath) {
130 
131         String         urlImage   = url.toLowerCase(Locale.ENGLISH);
132         HsqlProperties props      = new HsqlProperties();
133         HsqlProperties extraProps = null;
134         String         arguments  = null;
135         int            pos        = 0;
136         String         type       = null;
137         int            port       = 0;
138         String         database;
139         String         path;
140         boolean        isNetwork = false;
141 
142         if (hasPrefix) {
143             if (urlImage.startsWith(S_URL_PREFIX)) {
144                 pos = S_URL_PREFIX.length();
145             } else {
146                 return props;
147             }
148         }
149 
150         while (true) {
151             int replacePos = url.indexOf("${");
152 
153             if (replacePos == -1) {
154                 break;
155             }
156 
157             int endPos = url.indexOf("}", replacePos);
158 
159             if (endPos == -1) {
160                 break;
161             }
162 
163             String varName  = url.substring(replacePos + 2, endPos);
164             String varValue = null;
165 
166             try {
167                 varValue = System.getProperty(varName);
168             } catch (SecurityException e) {}
169 
170             if (varValue == null) {
171                 break;
172             }
173 
174             url = url.substring(0, replacePos) + varValue
175                   + url.substring(endPos + 1);
176             urlImage = url.toLowerCase(Locale.ENGLISH);
177         }
178 
179         props.setProperty("url", url);
180 
181         int postUrlPos = url.length();
182 
183         // postUrlPos is the END position in url String,
184         // wrt what remains to be processed.
185         // I.e., if postUrlPos is 100, url no longer needs to examined at
186         // index 100 or later.
187         int semiPos = url.indexOf(';', pos);
188 
189         if (semiPos > -1) {
190             arguments  = url.substring(semiPos + 1, urlImage.length());
191             postUrlPos = semiPos;
192             extraProps = HsqlProperties.delimitedArgPairsToProps(arguments,
193                     "=", ";", null);
194 
195             // validity checks are performed by engine
196             props.addProperties(extraProps);
197         }
198 
199         if (postUrlPos == pos + 1 && urlImage.startsWith(S_DOT, pos)) {
200             type = S_DOT;
201         } else if (urlImage.startsWith(S_MEM, pos)) {
202             type = S_MEM;
203         } else if (urlImage.startsWith(S_FILE, pos)) {
204             type = S_FILE;
205         } else if (urlImage.startsWith(S_RES, pos)) {
206             type = S_RES;
207         } else if (urlImage.startsWith(S_ALIAS, pos)) {
208             type = S_ALIAS;
209         } else if (urlImage.startsWith(S_HSQL, pos)) {
210             type      = S_HSQL;
211             port      = ServerConstants.SC_DEFAULT_HSQL_SERVER_PORT;
212             isNetwork = true;
213         } else if (urlImage.startsWith(S_HSQLS, pos)) {
214             type      = S_HSQLS;
215             port      = ServerConstants.SC_DEFAULT_HSQLS_SERVER_PORT;
216             isNetwork = true;
217         } else if (urlImage.startsWith(S_HTTP, pos)) {
218             type      = S_HTTP;
219             port      = ServerConstants.SC_DEFAULT_HTTP_SERVER_PORT;
220             isNetwork = true;
221         } else if (urlImage.startsWith(S_HTTPS, pos)) {
222             type      = S_HTTPS;
223             port      = ServerConstants.SC_DEFAULT_HTTPS_SERVER_PORT;
224             isNetwork = true;
225         }
226 
227         if (type == null) {
228             type = S_FILE;
229         } else if (type == S_DOT) {
230             type = S_MEM;
231 
232             // keep pos
233         } else {
234             pos += type.length();
235         }
236 
237         props.setProperty("connection_type", type);
238 
239         if (isNetwork) {
240 
241             // First capture 3 segments:  host + port + path
242             String pathSeg = null;
243             String hostSeg = null;
244             String portSeg = null;
245             int    endPos  = url.indexOf('/', pos);
246 
247             if (endPos > 0 && endPos < postUrlPos) {
248                 pathSeg = url.substring(endPos, postUrlPos);
249 
250                 // N.b. pathSeg necessarily begins with /.
251             } else {
252                 endPos = postUrlPos;
253             }
254 
255             // Processing different for ipv6 host address and all others:
256             if (url.charAt(pos) == '[') {
257 
258                 // ipv6
259                 int endIpv6 = url.indexOf(']', pos + 2);
260 
261                 // Notice 2 instead of 1 to require non-empty addr segment
262                 if (endIpv6 < 0 || endIpv6 >= endPos) {
263                     return null;
264 
265                     // Wish could throw something with a useful message for user
266                     // here.
267                 }
268 
269                 hostSeg = urlImage.substring(pos + 1, endIpv6);
270 
271                 if (endPos > endIpv6 + 1) {
272                     portSeg = url.substring(endIpv6 + 1, endPos);
273                 }
274             } else {
275 
276                 // non-ipv6
277                 int colPos = url.indexOf(':', pos + 1);
278 
279                 if (colPos > -1 && colPos < endPos) {
280 
281                     // portSeg will be non-empty, but could contain just ":"
282                     portSeg = url.substring(colPos, endPos);
283                 } else {
284                     colPos = -1;
285                 }
286 
287                 hostSeg = urlImage.substring(pos, (colPos > 0) ? colPos
288                                                                : endPos);
289             }
290 
291             // At this point, the entire url has been parsed into
292             // hostSeg + portSeg + pathSeg.
293             if (portSeg != null) {
294                 if (portSeg.length() < 2 || portSeg.charAt(0) != ':') {
295 
296                     // Wish could throw something with a useful message for user
297                     // here.
298                     return null;
299                 }
300 
301                 try {
302                     port = Integer.parseInt(portSeg.substring(1));
303                 } catch (NumberFormatException e) {
304 
305                     // System.err.println("NFE for (" + portSeg + ')'); debug
306                     return null;
307                 }
308             }
309 
310             if (noPath) {
311                 path     = "";
312                 database = pathSeg;
313             } else if (pathSeg == null) {
314                 path     = "/";
315                 database = "";
316             } else {
317                 int lastSlashPos = pathSeg.lastIndexOf('/');
318 
319                 if (lastSlashPos < 1) {
320                     path = "/";
321                     database =
322                         pathSeg.substring(1).toLowerCase(Locale.ENGLISH);
323                 } else {
324                     path     = pathSeg.substring(0, lastSlashPos);
325                     database = pathSeg.substring(lastSlashPos + 1);
326                 }
327             }
328 
329             /* Just for debug.  Remove once stable:
330             System.err.println("Host seg (" + hostSeg + "), Port val (" + port
331                     + "), Path val (" + pathSeg + "), path (" + path
332                     + "), db (" + database + ')');
333              */
334             props.setProperty("port", port);
335             props.setProperty("host", hostSeg);
336             props.setProperty("path", path);
337 
338             if (!noPath && extraProps != null) {
339                 String filePath = extraProps.getProperty("filepath");
340 
341                 if (filePath != null && database.length() != 0) {
342                     database += ";" + filePath;
343                 } else {
344                     if (url.indexOf(S_MEM) == postUrlPos + 1
345                             || url.indexOf(S_FILE) == postUrlPos + 1) {
346                         database += url.substring(postUrlPos);
347                     }
348                 }
349             }
350         } else {
351             if (type == S_MEM) {
352                 database = urlImage.substring(pos, postUrlPos);
353             } else if (type == S_RES) {
354                 database = url.substring(pos, postUrlPos);
355 
356                 if (database.indexOf('/') != 0) {
357                     database = '/' + database;
358                 }
359             } else {
360                 database = url.substring(pos, postUrlPos);
361 
362                 if (database.startsWith("~")) {
363                     String userHome = "~";
364 
365                     try {
366                         userHome = System.getProperty("user.home");
367                     } catch (SecurityException e) {}
368 
369                     database = userHome + database.substring(1);
370                 }
371             }
372 
373             if (database.length() == 0) {
374                 return null;
375             }
376         }
377 
378         pos = database.indexOf("&password=");
379 
380         if (pos != -1) {
381             String password = database.substring(pos + "&password=".length());
382 
383             props.setProperty("password", password);
384 
385             database = database.substring(0, pos);
386         }
387 
388         pos = database.indexOf("?user=");
389 
390         if (pos != -1) {
391             String user = database.substring(pos + "?user=".length());
392 
393             props.setProperty("user", user);
394 
395             database = database.substring(0, pos);
396         }
397 
398         props.setProperty("database", database);
399 
400         return props;
401     }
402 }
403