Changeset: 88c5b678e974 for monetdb-java
URL: https://dev.monetdb.org/hg/monetdb-java/rev/88c5b678e974
Added Files:
        src/main/java/org/monetdb/mcl/net/MonetUrlParser.java
        src/main/java/org/monetdb/mcl/net/Parameter.java
        src/main/java/org/monetdb/mcl/net/ParameterType.java
        src/main/java/org/monetdb/mcl/net/Target.java
        src/main/java/org/monetdb/mcl/net/ValidationError.java
        src/main/java/org/monetdb/mcl/net/Verify.java
        tests/UrlTester.java
        tests/tests.md
Branch: monetdbs
Log Message:

URL parser passes the tests.

Some tests had to change to accomodate Java.


diffs (truncated from 2674 to 300 lines):

diff --git a/src/main/java/org/monetdb/mcl/net/MonetUrlParser.java 
b/src/main/java/org/monetdb/mcl/net/MonetUrlParser.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/monetdb/mcl/net/MonetUrlParser.java
@@ -0,0 +1,305 @@
+package org.monetdb.mcl.net;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.Properties;
+
+public class MonetUrlParser {
+    private final Properties props;
+    private final String urlText;
+    private final URI url;
+    boolean userWasSet = false;
+    boolean passwordWasSet = false;
+
+    public MonetUrlParser(Properties props, String url) throws 
URISyntaxException {
+        this.props = props;
+        this.urlText = url;
+        this.url = new URI(url);
+    }
+
+    public static void parse(Properties props, String url) throws 
URISyntaxException {
+        boolean modern = true;
+        if (url.startsWith("mapi:")) {
+            modern = false;
+            url = url.substring(5);
+            if (url.equals("monetdb://")) {
+                // deal with peculiarity of Java's URI parser
+                url = "monetdb:///";
+            }
+
+        }
+        try {
+            MonetUrlParser parser = new MonetUrlParser(props, url);
+            if (modern) {
+                parser.parseModern();
+            } else {
+                parser.parseClassic();
+            }
+            if (parser.userWasSet && !parser.passwordWasSet) 
parser.clear(Parameter.PASSWORD);
+        } catch (URISyntaxException e) {
+            int idx = e.getIndex();
+            if (idx >= 0 && !modern) {
+                // "mapi:"
+                idx += 5;
+            }
+            throw new URISyntaxException(e.getInput(), e.getReason(), idx);
+        }
+    }
+
+    private static String percentDecode(String context, String text) throws 
URISyntaxException {
+        try {
+            return URLDecoder.decode(text, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalStateException("should be unreachable: UTF-8 
unknown??", e);
+        } catch (IllegalArgumentException e) {
+            throw new URISyntaxException(text, context + ": invalid percent 
escape");
+        }
+    }
+
+    private void set(Parameter parm, String value) {
+        parm = keyMagic(parm);
+        props.setProperty(parm.name, value != null ? value : "");
+    }
+
+    private void set(String key, String value) {
+        Parameter parm = Parameter.forName(key);
+        if (parm != null)
+            set(parm, value);
+        else
+            props.setProperty(key, value);
+    }
+
+    private void clear(Parameter parm) {
+        parm = keyMagic(parm);
+        String value = parm.type.format(Target.getDefault(parm));
+        props.setProperty(parm.name, value);
+    }
+
+    private Parameter keyMagic(Parameter key) {
+        switch (key) {
+            case USER:
+                userWasSet = true;
+                break;
+            case PASSWORD:
+                passwordWasSet = true;
+                break;
+            case FETCHSIZE:
+                key = Parameter.REPLYSIZE;
+                break;
+            default:
+                break;
+        }
+        return key;
+    }
+
+    private void parseModern() throws URISyntaxException {
+        clearBasic();
+
+        String scheme = url.getScheme();
+        if (scheme == null) throw new URISyntaxException(urlText, "URL scheme 
must be monetdb:// or monetdbs://");
+        switch (scheme) {
+            case "monetdb":
+                set(Parameter.TLS, "false");
+                break;
+            case "monetdbs":
+                set(Parameter.TLS, "true");
+                break;
+            default:
+                throw new URISyntaxException(urlText, "URL scheme must be 
monetdb:// or monetdbs://");
+        }
+
+        // The built-in getHost and getPort methods do strange things
+        // in edge cases such as percent-encoded host names and
+        // invalid port numbers
+        String authority = url.getAuthority();
+        String host;
+        String remainder;
+        int pos;
+        String raw = url.getRawSchemeSpecificPart();
+        if (authority == null) {
+            if (!url.getRawSchemeSpecificPart().startsWith("//")) {
+                throw new URISyntaxException(urlText, "expected //");
+            }
+            host = "";
+            remainder = "";
+        } else if (authority.startsWith("[")) {
+            // IPv6
+            pos = authority.indexOf(']');
+            if (pos < 0)
+                throw new URISyntaxException(urlText, "unmatched '['");
+            host = authority.substring(1, pos);
+            remainder = authority.substring(pos + 1);
+        } else if ((pos = authority.indexOf(':')) >= 0){
+            host = authority.substring(0, pos);
+            remainder = authority.substring(pos);
+        } else {
+            host = authority;
+            remainder = "";
+        }
+        switch (host) {
+            case "localhost":
+                set(Parameter.HOST, "");
+                break;
+            case "localhost.":
+                set(Parameter.HOST, "localhost");
+                break;
+            default:
+                set(Parameter.HOST, host);
+                break;
+        }
+
+        if (remainder.isEmpty()) {
+            // do nothing
+        } else if (remainder.startsWith(":")) {
+            String portStr = remainder.substring(1);
+            try {
+                int port = Integer.parseInt(portStr);
+                if (port <= 0 || port > 65535)
+                    portStr = null;
+            } catch (NumberFormatException e) {
+                portStr = null;
+            }
+            if (portStr == null)
+                throw new URISyntaxException(urlText, "invalid port number");
+            set(Parameter.PORT, portStr);
+        }
+
+        String path = url.getRawPath();
+        String[] parts = path.split("/", 5);
+        // <0: empty before leading slash> / <1: database> / <2: tableschema> 
/ <3: table> / <4: should not exist>
+        switch (parts.length) {
+            case 5:
+                throw new URISyntaxException(urlText, "table name should not 
contain slashes");
+            case 4:
+                set(Parameter.TABLE, percentDecode(Parameter.TABLE.name, 
parts[3]));
+                // fallthrough
+            case 3:
+                set(Parameter.TABLESCHEMA, 
percentDecode(Parameter.TABLESCHEMA.name, parts[2]));
+                // fallthrough
+            case 2:
+                set(Parameter.DATABASE, percentDecode(Parameter.DATABASE.name, 
parts[1]));
+            case 1:
+            case 0:
+                // fallthrough
+                break;
+        }
+
+        final String query = url.getRawQuery();
+        if (query != null) {
+            final String args[] = query.split("&");
+            for (int i = 0; i < args.length; i++) {
+                pos = args[i].indexOf('=');
+                if (pos <= 0) {
+                    throw new URISyntaxException(args[i], "invalid key=value 
pair");
+                }
+                String key = args[i].substring(0, pos);
+                key = percentDecode(key, key);
+                Parameter parm = Parameter.forName(key);
+                if (parm != null && parm.isCore)
+                    throw new URISyntaxException(key, key + "= is not allowed 
as a query parameter");
+
+                String value = args[i].substring(pos + 1);
+                set(key, percentDecode(key, value));
+            }
+        }
+    }
+
+
+    private void parseClassic() throws URISyntaxException {
+        String scheme = url.getScheme();
+        if (scheme == null) throw new URISyntaxException(urlText, "URL scheme 
must be mapi:monetdb:// or mapi:merovingian://");
+        switch (scheme) {
+            case "monetdb":
+                clearBasic();
+                break;
+            case "merovingian":
+                throw new IllegalStateException("mapi:merovingian: not 
supported yet");
+            default:
+                throw new URISyntaxException(urlText, "URL scheme must be 
mapi:monetdb:// or mapi:merovingian://");
+        }
+
+        if (!url.getRawSchemeSpecificPart().startsWith("//")) {
+            throw new URISyntaxException(urlText, "expected //");
+        }
+
+        String authority = url.getRawAuthority();
+        String host;
+        String portStr;
+        int pos;
+        if (authority == null) {
+            host = "";
+            portStr = "";
+        } else if (authority.indexOf('@') >= 0) {
+            throw new URISyntaxException(urlText, "user@host syntax is not 
allowed");
+        } else if ((pos = authority.indexOf(':')) >= 0) {
+            host = authority.substring(0, pos);
+            portStr = authority.substring(pos + 1);
+        } else {
+            host = authority;
+            portStr = "";
+        }
+
+        if (!portStr.isEmpty()) {
+            int port;
+            try {
+                port = Integer.parseInt(portStr);
+            } catch (NumberFormatException e) {
+                port = -1;
+            }
+            if (port <= 0) {
+                throw new URISyntaxException(urlText, "invalid port number");
+            }
+            set(Parameter.PORT, portStr);
+        }
+
+        String path = url.getRawPath();
+        boolean isUnix;
+        if (host.isEmpty() && portStr.isEmpty()) {
+            // socket
+            isUnix = true;
+            clear(Parameter.HOST);
+            set(Parameter.SOCK, path != null ? path : "");
+        } else {
+            // tcp
+            isUnix = false;
+            clear(Parameter.SOCK);
+            set(Parameter.HOST, host);
+            if (path == null || path.isEmpty()) {
+                // do nothing
+            } else if (!path.startsWith("/")) {
+                throw new URISyntaxException(urlText, "expect path to start 
with /");
+            } else {
+                String database = path.substring(1);
+                if (database.contains("/"))
+                    throw new URISyntaxException(urlText, "no slashes allowed 
in database name");
+                set(Parameter.DATABASE, database);
+            }
+        }
+
+        final String query = url.getRawQuery();
+        if (query != null) {
+            final String args[] = query.split("&");
+            for (int i = 0; i < args.length; i++) {
+                String arg = args[i];
+                if (arg.startsWith("language=")) {
+                    String language = arg.substring(9);
+                    set(Parameter.LANGUAGE, language);
+                } else if (arg.startsWith("database=")) {
+                    String database = arg.substring(9);
+                    set(Parameter.DATABASE, database);
+                } else {
+                    // ignore
+                }
+            }
_______________________________________________
checkin-list mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to