Mercurial > hg > monetdb-java
diff src/main/java/org/monetdb/mcl/net/Target.java @ 789:88c5b678e974 monetdbs
URL parser passes the tests.
Some tests had to change to accomodate Java.
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Wed, 29 Nov 2023 13:28:52 +0100 (17 months ago) |
parents | |
children | 547eca89fc5e |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/main/java/org/monetdb/mcl/net/Target.java @@ -0,0 +1,330 @@ +package org.monetdb.mcl.net; + +import java.util.Calendar; +import java.util.Properties; +import java.util.regex.Pattern; + +public class Target { + private static Pattern namePattern = Pattern.compile("^[a-zA-Z_][-a-zA-Z0-9_.]*$"); + private static Pattern hashPattern = Pattern.compile("^sha256:[0-9a-fA-F:]*$"); + private final boolean tls; + private final String host; + private final int port; + private final String database; + private final String tableschema; + private final String table; + private final String sock; + private final String sockdir; + private final String cert; + private final String certhash; + private final String clientkey; + private final String clientcert; + private final String user; + private final String password; + private final String language; + private final boolean autocommit; + private final String schema; + private final int timezone; + private final int binary; + private final int replysize; + private final String hash; + private final boolean debug; + private final String logfile; + + public Target(Properties properties) throws ValidationError { + + // 1. The parameters have the types listed in the table in [Section + // Parameters](#parameters). + tls = validateBoolean(properties, Parameter.TLS); + host = validateString(properties, Parameter.HOST); + port = validateInt(properties, Parameter.PORT); + database = validateString(properties, Parameter.DATABASE); + tableschema = validateString(properties, Parameter.TABLESCHEMA); + table = validateString(properties, Parameter.TABLE); + sock = validateString(properties, Parameter.SOCK); + sockdir = validateString(properties, Parameter.SOCKDIR); + cert = validateString(properties, Parameter.CERT); + certhash = validateString(properties, Parameter.CERTHASH); + clientkey = validateString(properties, Parameter.CLIENTKEY); + clientcert = validateString(properties, Parameter.CLIENTCERT); + user = validateString(properties, Parameter.USER); + password = validateString(properties, Parameter.PASSWORD); + language = validateString(properties, Parameter.LANGUAGE); + autocommit = validateBoolean(properties, Parameter.AUTOCOMMIT); + schema = validateString(properties, Parameter.SCHEMA); + timezone = validateInt(properties, Parameter.TIMEZONE); + replysize = validateInt(properties, Parameter.REPLYSIZE); + hash = validateString(properties, Parameter.HASH); + debug = validateBoolean(properties, Parameter.DEBUG); + logfile = validateString(properties, Parameter.LOGFILE); + + for (String name: properties.stringPropertyNames()) { + if (Parameter.forName(name) != null) + continue; + if (name.contains("_")) + continue; + throw new ValidationError("unknown parameter: " + name); + } + + String binaryString = validateString(properties, Parameter.BINARY); + int binaryInt; + try { + binaryInt = (int) ParameterType.Int.parse(Parameter.BINARY.name, binaryString); + } catch (ValidationError e) { + try { + boolean b = (boolean) ParameterType.Bool.parse(Parameter.BINARY.name, binaryString); + binaryInt = b ? 65535 : 0; + } catch (ValidationError ee) { + throw new ValidationError("binary= must be either a number or true/yes/on/false/no/off"); + } + } + if (binaryInt < 0) + throw new ValidationError("binary= cannot be negative"); + binary = binaryInt; + + + // 2. At least one of **sock** and **host** must be empty. + if (!sock.isEmpty() && !host.isEmpty()) + throw new ValidationError("sock=" + sock + " cannot be combined with host=" + host); + + // 3. The string parameter **binary** must either parse as a boolean or as a + // non-negative integer. + // + // (checked above) + + // 4. If **sock** is not empty, **tls** must be 'off'. + if (!sock.isEmpty() && tls) throw new ValidationError("monetdbs:// cannot be combined with sock="); + + // 5. If **certhash** is not empty, it must be of the form `{sha256}hexdigits` + // where hexdigits is a non-empty sequence of 0-9, a-f, A-F and colons. + // TODO + if (!certhash.isEmpty()) { + if (!certhash.toLowerCase().startsWith("sha256:")) + throw new ValidationError("certificate hash must start with 'sha256:'"); + if (!hashPattern.matcher(certhash).matches()) + throw new ValidationError("invalid certificate hash"); + } + + // 6. If **tls** is 'off', **cert** and **certhash** must be 'off' as well. + if (!tls) { + if (!cert.isEmpty() || !certhash.isEmpty()) + throw new ValidationError("cert= and certhash= are only allowed in combination with monetdbs://"); + } + + // 7. Parameters **database**, **tableschema** and **table** must consist only of + // upper- and lowercase letters, digits, periods, dashes and underscores. They must not + // start with a dash. + // If **table** is not empty, **tableschema** must also not be empty. + // If **tableschema** is not empty, **database** must also not be empty. + if (database.isEmpty() && !tableschema.isEmpty()) + throw new ValidationError("table schema cannot be set without database"); + if (tableschema.isEmpty() && !table.isEmpty()) + throw new ValidationError("table cannot be set without schema"); + if (!database.isEmpty() && !namePattern.matcher(database).matches()) + throw new ValidationError("invalid database name"); + if (!tableschema.isEmpty() && !namePattern.matcher(tableschema).matches()) + throw new ValidationError("invalid table schema name"); + if (!table.isEmpty() && !namePattern.matcher(table).matches()) + throw new ValidationError("invalid table name"); + + + // 8. Parameter **port** must be -1 or in the range 1-65535. + if (port < -1 || port == 0 || port > 65535) throw new ValidationError("invalid port number " + port); + + // 9. If **clientcert** is set, **clientkey** must also be set. + if (!clientcert.isEmpty() && clientkey.isEmpty()) + throw new ValidationError("clientcert= is only valid in combination with clientkey="); + } + + public static boolean validateBoolean(Properties props, Parameter parm) throws ValidationError { + Object value = props.get(parm.name); + if (value != null) { + return (Boolean) parm.type.parse(parm.name, (String) value); + } else { + return (Boolean) getDefault(parm); + } + } + + public static int validateInt(Properties props, Parameter parm) throws ValidationError { + Object value = props.get(parm.name); + if (value != null) { + return (Integer) parm.type.parse(parm.name, (String) value); + } else { + return (Integer) getDefault(parm); + } + } + + public static String validateString(Properties props, Parameter parm) throws ValidationError { + Object value = props.get(parm.name); + if (value != null) { + return (String) parm.type.parse(parm.name, (String) value); + } else { + return (String) getDefault(parm); + } + } + + private static int timezone() { + Calendar cal = Calendar.getInstance(); + int offsetMillis = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); + int offsetSeconds = offsetMillis / 1000; + return offsetSeconds; + } + + public static Object getDefault(Parameter parm) { + if (parm == Parameter.TIMEZONE) return timezone(); + else return parm.defaultValue; + } + + public static Properties defaultProperties() { + Properties props = new Properties(); + return props; + } + + public boolean getTls() { + return tls; + } + + // Getter is private because you probably want connectTcp() instead + private String getHost() { + return host; + } + + // Getter is private because you probably want connectPort() instead + private int getPort() { + return port; + } + + public String getDatabase() { + return database; + } + + public String getTableschema() { + return tableschema; + } + + public String getTable() { + return table; + } + + // Getter is private because you probably want connectUnix() instead + private String getSock() { + return sock; + } + + public String getSockdir() { + return sockdir; + } + + public String getCert() { + return cert; + } + + public String getCerthash() { + return certhash; + } + + public String getClientkey() { + return clientkey; + } + + public String getClientcert() { + return clientcert; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public String getLanguage() { + return language; + } + + public boolean getAutocommit() { + return autocommit; + } + + public String getSchema() { + return schema; + } + + public int getTimezone() { + return timezone; + } + + // Getter is private because you probably want connectBinary() instead + public int getBinary() { + return binary; + } + + public int getReplysize() { + return replysize; + } + + public String getHash() { + return hash; + } + + public boolean getDebug() { + return debug; + } + + public String getLogfile() { + return logfile; + } + + public boolean connectScan() { + if (database.isEmpty()) return false; + if (!sock.isEmpty() || !host.isEmpty() || port != -1) return false; + return !tls; + } + + public int connectPort() { + return port == -1 ? 50000 : port; + } + + public String connectUnix() { + if (!sock.isEmpty()) return sock; + if (tls) return ""; + if (host.isEmpty()) return sockdir + "/.s.monetdb." + connectPort(); + return ""; + } + + public String connectTcp() { + if (!sock.isEmpty()) return ""; + if (host.isEmpty()) return "localhost"; + return host; + } + + public Verify connectVerify() { + if (!tls) return Verify.None; + if (!certhash.isEmpty()) return Verify.Hash; + if (!cert.isEmpty()) return Verify.Cert; + return Verify.System; + } + + public String connectCertHashDigits() { + if (!tls) return null; + StringBuilder builder = new StringBuilder(certhash.length()); + for (int i = "sha256:".length(); i < certhash.length(); i++) { + char c = certhash.charAt(i); + if (Character.digit(c, 16) >= 0) builder.append(Character.toLowerCase(c)); + } + return builder.toString(); + } + + public int connectBinary() { + return binary; + } + + public String connectClientKey() { + return clientkey; + } + + public String connectClientCert() { + return clientcert.isEmpty() ? clientkey : clientcert; + } +}