Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/mcl/net/MonetUrlParser.java @ 834:5aa19bbed0d6 monetdbs
Comments and formatting
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Wed, 13 Dec 2023 15:39:47 +0100 (16 months ago) |
parents | 6b7778153d23 |
children | 9d21c6e7ed26 |
line wrap: on
line source
package org.monetdb.mcl.net; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; /** * Helper class to keep the URL parsing code separate from the rest of * the {@link Target} class. */ public class MonetUrlParser { private final Target target; private final String urlText; private final URI url; private MonetUrlParser(Target target, String url) throws URISyntaxException { this.target = target; this.urlText = url; // we want to accept monetdb:// but the Java URI parser rejects that. switch (url) { case "monetdb:-": case "monetdbs:-": throw new URISyntaxException(url, "invalid MonetDB URL"); case "monetdb://": case "monetdbs://": url += "-"; break; } this.url = new URI(url); } public static void parse(Target target, String url) throws URISyntaxException, ValidationError { if (url.equals("monetdb://")) { // deal with peculiarity of Java's URI parser url = "monetdb:///"; } target.barrier(); if (url.startsWith("mapi:")) { try { MonetUrlParser parser = new MonetUrlParser(target, url.substring(5)); parser.parseClassic(); } catch (URISyntaxException e) { URISyntaxException exc = new URISyntaxException(e.getInput(), e.getReason(), -1); exc.setStackTrace(e.getStackTrace()); throw exc; } } else { MonetUrlParser parser = new MonetUrlParser(target, url); parser.parseModern(); } target.barrier(); } public 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"); } } public static String percentEncode(String text) { try { return URLEncoder.encode(text, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private void parseModern() throws URISyntaxException, ValidationError { clearBasic(); String scheme = url.getScheme(); if (scheme == null) throw new URISyntaxException(urlText, "URL scheme must be monetdb:// or monetdbs://"); switch (scheme) { case "monetdb": target.setTls(false); break; case "monetdbs": target.setTls(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; if (authority == null) { if (!url.getRawSchemeSpecificPart().startsWith("//")) { throw new URISyntaxException(urlText, "expected //"); } host = ""; remainder = ""; } else if (authority.equals("-")) { 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 = ""; } } host = Target.unpackHost(host); target.setHost(host); 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 ValidationError(urlText, "invalid port number"); target.setString(Parameter.PORT, portStr); } String path = url.getRawPath(); String[] parts = path.split("/", 4); // <0: empty before leading slash> / <1: database> / <2: tableschema> / <3: table> / <4: should not exist> switch (parts.length) { case 4: target.setString(Parameter.TABLE, percentDecode(Parameter.TABLE.name, parts[3])); // fallthrough case 3: target.setString(Parameter.TABLESCHEMA, percentDecode(Parameter.TABLESCHEMA.name, parts[2])); // fallthrough case 2: target.setString(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); target.setString(key, percentDecode(key, value)); } } } private void parseClassic() throws URISyntaxException, ValidationError { if (!url.getRawSchemeSpecificPart().startsWith("//")) { throw new URISyntaxException(urlText, "expected //"); } String scheme = url.getScheme(); if (scheme == null) scheme = ""; switch (scheme) { case "monetdb": parseClassicAuthorityAndPath(); break; case "merovingian": String authority = url.getRawAuthority(); // authority must be "proxy" ignore authority and path boolean valid = urlText.startsWith("merovingian://proxy?") || urlText.equals("merovingian://proxy"); if (!valid) throw new URISyntaxException(urlText, "with mapi:merovingian:, only //proxy is supported"); break; default: throw new URISyntaxException(urlText, "URL scheme must be mapi:monetdb:// or mapi:merovingian://"); } 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); target.setString(Parameter.LANGUAGE, language); } else if (arg.startsWith("database=")) { String database = arg.substring(9); target.setString(Parameter.DATABASE, database); } else { // ignore } } } } private void parseClassicAuthorityAndPath() throws URISyntaxException, ValidationError { clearBasic(); 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 ValidationError(urlText, "invalid port number"); } target.setString(Parameter.PORT, portStr); } String path = url.getRawPath(); if (host.isEmpty() && portStr.isEmpty()) { // socket target.clear(Parameter.HOST); target.setString(Parameter.SOCK, path != null ? path : ""); } else { // tcp target.clear(Parameter.SOCK); target.setString(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); target.setString(Parameter.DATABASE, database); } } } private void clearBasic() { target.clear(Parameter.TLS); target.clear(Parameter.HOST); target.clear(Parameter.PORT); target.clear(Parameter.DATABASE); } }