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