Mercurial > hg > monetdb-java
comparison 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 (17 months ago) |
parents | 6b7778153d23 |
children | 9d21c6e7ed26 |
comparison
equal
deleted
inserted
replaced
813:a71afa48f269 | 834:5aa19bbed0d6 |
---|---|
4 import java.net.URI; | 4 import java.net.URI; |
5 import java.net.URISyntaxException; | 5 import java.net.URISyntaxException; |
6 import java.net.URLDecoder; | 6 import java.net.URLDecoder; |
7 import java.net.URLEncoder; | 7 import java.net.URLEncoder; |
8 | 8 |
9 /** | |
10 * Helper class to keep the URL parsing code separate from the rest of | |
11 * the {@link Target} class. | |
12 */ | |
9 public class MonetUrlParser { | 13 public class MonetUrlParser { |
10 private final Target target; | 14 private final Target target; |
11 private final String urlText; | 15 private final String urlText; |
12 private final URI url; | 16 private final URI url; |
13 | 17 |
14 public MonetUrlParser(Target target, String url) throws URISyntaxException { | 18 private MonetUrlParser(Target target, String url) throws URISyntaxException { |
15 this.target = target; | 19 this.target = target; |
16 this.urlText = url; | 20 this.urlText = url; |
17 // we want to accept monetdb:// but the Java URI parser rejects that. | 21 // we want to accept monetdb:// but the Java URI parser rejects that. |
18 switch (url) { | 22 switch (url) { |
19 case "monetdb:-": | 23 case "monetdb:-": |
20 case "monetdbs:-": | 24 case "monetdbs:-": |
21 throw new URISyntaxException(url, "invalid MonetDB URL"); | 25 throw new URISyntaxException(url, "invalid MonetDB URL"); |
22 case "monetdb://": | 26 case "monetdb://": |
23 case "monetdbs://": | 27 case "monetdbs://": |
24 url += "-"; | 28 url += "-"; |
25 break; | 29 break; |
26 } | 30 } |
27 this.url = new URI(url); | 31 this.url = new URI(url); |
28 } | 32 } |
29 | 33 |
30 public static void parse(Target target, String url) throws URISyntaxException, ValidationError { | 34 public static void parse(Target target, String url) throws URISyntaxException, ValidationError { |
31 if (url.equals("monetdb://")) { | 35 if (url.equals("monetdb://")) { |
32 // deal with peculiarity of Java's URI parser | 36 // deal with peculiarity of Java's URI parser |
33 url = "monetdb:///"; | 37 url = "monetdb:///"; |
34 } | 38 } |
35 | 39 |
36 target.barrier(); | 40 target.barrier(); |
37 if (url.startsWith("mapi:")) { | 41 if (url.startsWith("mapi:")) { |
38 try { | 42 try { |
39 MonetUrlParser parser = new MonetUrlParser(target, url.substring(5)); | 43 MonetUrlParser parser = new MonetUrlParser(target, url.substring(5)); |
40 parser.parseClassic(); | 44 parser.parseClassic(); |
41 } catch (URISyntaxException e) { | 45 } catch (URISyntaxException e) { |
42 URISyntaxException exc = new URISyntaxException(e.getInput(), e.getReason(), -1); | 46 URISyntaxException exc = new URISyntaxException(e.getInput(), e.getReason(), -1); |
43 exc.setStackTrace(e.getStackTrace()); | 47 exc.setStackTrace(e.getStackTrace()); |
44 throw exc; | 48 throw exc; |
45 } | 49 } |
46 } else { | 50 } else { |
47 MonetUrlParser parser = new MonetUrlParser(target, url); | 51 MonetUrlParser parser = new MonetUrlParser(target, url); |
48 parser.parseModern(); | 52 parser.parseModern(); |
49 } | 53 } |
50 target.barrier(); | 54 target.barrier(); |
51 } | 55 } |
52 | 56 |
53 public static String percentDecode(String context, String text) throws URISyntaxException { | 57 public static String percentDecode(String context, String text) throws URISyntaxException { |
54 try { | 58 try { |
55 return URLDecoder.decode(text, "UTF-8"); | 59 return URLDecoder.decode(text, "UTF-8"); |
56 } catch (UnsupportedEncodingException e) { | 60 } catch (UnsupportedEncodingException e) { |
57 throw new IllegalStateException("should be unreachable: UTF-8 unknown??", e); | 61 throw new IllegalStateException("should be unreachable: UTF-8 unknown??", e); |
58 } catch (IllegalArgumentException e) { | 62 } catch (IllegalArgumentException e) { |
59 throw new URISyntaxException(text, context + ": invalid percent escape"); | 63 throw new URISyntaxException(text, context + ": invalid percent escape"); |
60 } | 64 } |
61 } | 65 } |
62 | 66 |
63 public static String percentEncode(String text) { | 67 public static String percentEncode(String text) { |
64 try { | 68 try { |
65 return URLEncoder.encode(text, "UTF-8"); | 69 return URLEncoder.encode(text, "UTF-8"); |
66 } catch (UnsupportedEncodingException e) { | 70 } catch (UnsupportedEncodingException e) { |
67 throw new RuntimeException(e); | 71 throw new RuntimeException(e); |
68 } | 72 } |
69 } | 73 } |
70 | 74 |
71 private void parseModern() throws URISyntaxException, ValidationError { | 75 private void parseModern() throws URISyntaxException, ValidationError { |
72 clearBasic(); | 76 clearBasic(); |
73 | 77 |
74 String scheme = url.getScheme(); | 78 String scheme = url.getScheme(); |
75 if (scheme == null) throw new URISyntaxException(urlText, "URL scheme must be monetdb:// or monetdbs://"); | 79 if (scheme == null) |
76 switch (scheme) { | 80 throw new URISyntaxException(urlText, "URL scheme must be monetdb:// or monetdbs://"); |
77 case "monetdb": | 81 switch (scheme) { |
78 target.setTls(false); | 82 case "monetdb": |
79 break; | 83 target.setTls(false); |
80 case "monetdbs": | 84 break; |
81 target.setTls(true); | 85 case "monetdbs": |
82 break; | 86 target.setTls(true); |
83 default: | 87 break; |
84 throw new URISyntaxException(urlText, "URL scheme must be monetdb:// or monetdbs://"); | 88 default: |
85 } | 89 throw new URISyntaxException(urlText, "URL scheme must be monetdb:// or monetdbs://"); |
86 | 90 } |
87 // The built-in getHost and getPort methods do strange things | 91 |
88 // in edge cases such as percent-encoded host names and | 92 // The built-in getHost and getPort methods do strange things |
89 // invalid port numbers | 93 // in edge cases such as percent-encoded host names and |
90 String authority = url.getAuthority(); | 94 // invalid port numbers |
91 String host; | 95 String authority = url.getAuthority(); |
92 String remainder; | 96 String host; |
93 int pos; | 97 String remainder; |
94 if (authority == null) { | 98 int pos; |
95 if (!url.getRawSchemeSpecificPart().startsWith("//")) { | 99 if (authority == null) { |
96 throw new URISyntaxException(urlText, "expected //"); | 100 if (!url.getRawSchemeSpecificPart().startsWith("//")) { |
97 } | 101 throw new URISyntaxException(urlText, "expected //"); |
98 host = ""; | 102 } |
99 remainder = ""; | 103 host = ""; |
100 } else if (authority.equals("-")) { | 104 remainder = ""; |
101 host = ""; | 105 } else if (authority.equals("-")) { |
102 remainder = ""; | 106 host = ""; |
103 } else { | 107 remainder = ""; |
104 if (authority.startsWith("[")) { | 108 } else { |
105 // IPv6 | 109 if (authority.startsWith("[")) { |
106 pos = authority.indexOf(']'); | 110 // IPv6 |
107 if (pos < 0) | 111 pos = authority.indexOf(']'); |
108 throw new URISyntaxException(urlText, "unmatched '['"); | 112 if (pos < 0) |
109 host = authority.substring(1, pos); | 113 throw new URISyntaxException(urlText, "unmatched '['"); |
110 remainder = authority.substring(pos + 1); | 114 host = authority.substring(1, pos); |
111 } else if ((pos = authority.indexOf(':')) >= 0) { | 115 remainder = authority.substring(pos + 1); |
112 host = authority.substring(0, pos); | 116 } else if ((pos = authority.indexOf(':')) >= 0) { |
113 remainder = authority.substring(pos); | 117 host = authority.substring(0, pos); |
114 } else { | 118 remainder = authority.substring(pos); |
115 host = authority; | 119 } else { |
116 remainder = ""; | 120 host = authority; |
117 } | 121 remainder = ""; |
118 } | 122 } |
119 host = Target.unpackHost(host); | 123 } |
120 target.setHost(host); | 124 host = Target.unpackHost(host); |
121 | 125 target.setHost(host); |
122 if (remainder.isEmpty()) { | 126 |
123 // do nothing | 127 if (remainder.isEmpty()) { |
124 } else if (remainder.startsWith(":")) { | 128 // do nothing |
125 String portStr = remainder.substring(1); | 129 } else if (remainder.startsWith(":")) { |
126 try { | 130 String portStr = remainder.substring(1); |
127 int port = Integer.parseInt(portStr); | 131 try { |
128 if (port <= 0 || port > 65535) | 132 int port = Integer.parseInt(portStr); |
129 portStr = null; | 133 if (port <= 0 || port > 65535) |
130 } catch (NumberFormatException e) { | 134 portStr = null; |
131 portStr = null; | 135 } catch (NumberFormatException e) { |
132 } | 136 portStr = null; |
133 if (portStr == null) | 137 } |
134 throw new ValidationError(urlText, "invalid port number"); | 138 if (portStr == null) |
135 target.setString(Parameter.PORT, portStr); | 139 throw new ValidationError(urlText, "invalid port number"); |
136 } | 140 target.setString(Parameter.PORT, portStr); |
137 | 141 } |
138 String path = url.getRawPath(); | 142 |
139 String[] parts = path.split("/", 4); | 143 String path = url.getRawPath(); |
140 // <0: empty before leading slash> / <1: database> / <2: tableschema> / <3: table> / <4: should not exist> | 144 String[] parts = path.split("/", 4); |
141 switch (parts.length) { | 145 // <0: empty before leading slash> / <1: database> / <2: tableschema> / <3: table> / <4: should not exist> |
142 case 4: | 146 switch (parts.length) { |
143 target.setString(Parameter.TABLE, percentDecode(Parameter.TABLE.name, parts[3])); | 147 case 4: |
144 // fallthrough | 148 target.setString(Parameter.TABLE, percentDecode(Parameter.TABLE.name, parts[3])); |
145 case 3: | 149 // fallthrough |
146 target.setString(Parameter.TABLESCHEMA, percentDecode(Parameter.TABLESCHEMA.name, parts[2])); | 150 case 3: |
147 // fallthrough | 151 target.setString(Parameter.TABLESCHEMA, percentDecode(Parameter.TABLESCHEMA.name, parts[2])); |
148 case 2: | 152 // fallthrough |
149 target.setString(Parameter.DATABASE, percentDecode(Parameter.DATABASE.name, parts[1])); | 153 case 2: |
150 case 1: | 154 target.setString(Parameter.DATABASE, percentDecode(Parameter.DATABASE.name, parts[1])); |
151 case 0: | 155 case 1: |
152 // fallthrough | 156 case 0: |
153 break; | 157 // fallthrough |
154 } | 158 break; |
155 | 159 } |
156 final String query = url.getRawQuery(); | 160 |
157 if (query != null) { | 161 final String query = url.getRawQuery(); |
158 final String args[] = query.split("&"); | 162 if (query != null) { |
159 for (int i = 0; i < args.length; i++) { | 163 final String[] args = query.split("&"); |
160 pos = args[i].indexOf('='); | 164 for (int i = 0; i < args.length; i++) { |
161 if (pos <= 0) { | 165 pos = args[i].indexOf('='); |
162 throw new URISyntaxException(args[i], "invalid key=value pair"); | 166 if (pos <= 0) { |
163 } | 167 throw new URISyntaxException(args[i], "invalid key=value pair"); |
164 String key = args[i].substring(0, pos); | 168 } |
165 key = percentDecode(key, key); | 169 String key = args[i].substring(0, pos); |
166 Parameter parm = Parameter.forName(key); | 170 key = percentDecode(key, key); |
167 if (parm != null && parm.isCore) | 171 Parameter parm = Parameter.forName(key); |
168 throw new URISyntaxException(key, key + "= is not allowed as a query parameter"); | 172 if (parm != null && parm.isCore) |
169 | 173 throw new URISyntaxException(key, key + "= is not allowed as a query parameter"); |
170 String value = args[i].substring(pos + 1); | 174 |
171 target.setString(key, percentDecode(key, value)); | 175 String value = args[i].substring(pos + 1); |
172 } | 176 target.setString(key, percentDecode(key, value)); |
173 } | 177 } |
174 } | 178 } |
175 | 179 } |
176 private void parseClassic() throws URISyntaxException, ValidationError { | 180 |
177 if (!url.getRawSchemeSpecificPart().startsWith("//")) { | 181 private void parseClassic() throws URISyntaxException, ValidationError { |
178 throw new URISyntaxException(urlText, "expected //"); | 182 if (!url.getRawSchemeSpecificPart().startsWith("//")) { |
179 } | 183 throw new URISyntaxException(urlText, "expected //"); |
180 | 184 } |
181 String scheme = url.getScheme(); | 185 |
182 if (scheme == null) | 186 String scheme = url.getScheme(); |
183 scheme = ""; | 187 if (scheme == null) |
184 switch (scheme) { | 188 scheme = ""; |
185 case "monetdb": | 189 switch (scheme) { |
186 parseClassicAuthorityAndPath(); | 190 case "monetdb": |
187 break; | 191 parseClassicAuthorityAndPath(); |
188 case "merovingian": | 192 break; |
189 String authority = url.getRawAuthority(); | 193 case "merovingian": |
190 // authority must be "proxy" ignore authority and path | 194 String authority = url.getRawAuthority(); |
191 boolean valid = urlText.startsWith("merovingian://proxy?") || urlText.equals("merovingian://proxy"); | 195 // authority must be "proxy" ignore authority and path |
192 if (!valid) | 196 boolean valid = urlText.startsWith("merovingian://proxy?") || urlText.equals("merovingian://proxy"); |
193 throw new URISyntaxException(urlText, "with mapi:merovingian:, only //proxy is supported"); | 197 if (!valid) |
194 break; | 198 throw new URISyntaxException(urlText, "with mapi:merovingian:, only //proxy is supported"); |
195 default: | 199 break; |
196 throw new URISyntaxException(urlText, "URL scheme must be mapi:monetdb:// or mapi:merovingian://"); | 200 default: |
197 } | 201 throw new URISyntaxException(urlText, "URL scheme must be mapi:monetdb:// or mapi:merovingian://"); |
198 | 202 } |
199 final String query = url.getRawQuery(); | 203 |
200 if (query != null) { | 204 final String query = url.getRawQuery(); |
201 final String args[] = query.split("&"); | 205 if (query != null) { |
202 for (int i = 0; i < args.length; i++) { | 206 final String[] args = query.split("&"); |
203 String arg = args[i]; | 207 for (int i = 0; i < args.length; i++) { |
204 if (arg.startsWith("language=")) { | 208 String arg = args[i]; |
205 String language = arg.substring(9); | 209 if (arg.startsWith("language=")) { |
206 target.setString(Parameter.LANGUAGE, language); | 210 String language = arg.substring(9); |
207 } else if (arg.startsWith("database=")) { | 211 target.setString(Parameter.LANGUAGE, language); |
208 String database = arg.substring(9); | 212 } else if (arg.startsWith("database=")) { |
209 target.setString(Parameter.DATABASE, database); | 213 String database = arg.substring(9); |
210 } else { | 214 target.setString(Parameter.DATABASE, database); |
211 // ignore | 215 } else { |
212 } | 216 // ignore |
213 } | 217 } |
214 } | 218 } |
215 } | 219 } |
216 | 220 } |
217 private void parseClassicAuthorityAndPath() throws URISyntaxException, ValidationError { | 221 |
218 clearBasic(); | 222 private void parseClassicAuthorityAndPath() throws URISyntaxException, ValidationError { |
219 String authority = url.getRawAuthority(); | 223 clearBasic(); |
220 String host; | 224 String authority = url.getRawAuthority(); |
221 String portStr; | 225 String host; |
222 int pos; | 226 String portStr; |
223 if (authority == null) { | 227 int pos; |
224 host = ""; | 228 if (authority == null) { |
225 portStr = ""; | 229 host = ""; |
226 } else if (authority.indexOf('@') >= 0) { | 230 portStr = ""; |
227 throw new URISyntaxException(urlText, "user@host syntax is not allowed"); | 231 } else if (authority.indexOf('@') >= 0) { |
228 } else if ((pos = authority.indexOf(':')) >= 0) { | 232 throw new URISyntaxException(urlText, "user@host syntax is not allowed"); |
229 host = authority.substring(0, pos); | 233 } else if ((pos = authority.indexOf(':')) >= 0) { |
230 portStr = authority.substring(pos + 1); | 234 host = authority.substring(0, pos); |
231 } else { | 235 portStr = authority.substring(pos + 1); |
232 host = authority; | 236 } else { |
233 portStr = ""; | 237 host = authority; |
234 } | 238 portStr = ""; |
235 | 239 } |
236 if (!portStr.isEmpty()) { | 240 |
237 int port; | 241 if (!portStr.isEmpty()) { |
238 try { | 242 int port; |
239 port = Integer.parseInt(portStr); | 243 try { |
240 } catch (NumberFormatException e) { | 244 port = Integer.parseInt(portStr); |
241 port = -1; | 245 } catch (NumberFormatException e) { |
242 } | 246 port = -1; |
243 if (port <= 0) { | 247 } |
244 throw new ValidationError(urlText, "invalid port number"); | 248 if (port <= 0) { |
245 } | 249 throw new ValidationError(urlText, "invalid port number"); |
246 target.setString(Parameter.PORT, portStr); | 250 } |
247 } | 251 target.setString(Parameter.PORT, portStr); |
248 | 252 } |
249 String path = url.getRawPath(); | 253 |
250 if (host.isEmpty() && portStr.isEmpty()) { | 254 String path = url.getRawPath(); |
251 // socket | 255 if (host.isEmpty() && portStr.isEmpty()) { |
252 target.clear(Parameter.HOST); | 256 // socket |
253 target.setString(Parameter.SOCK, path != null ? path : ""); | 257 target.clear(Parameter.HOST); |
254 } else { | 258 target.setString(Parameter.SOCK, path != null ? path : ""); |
255 // tcp | 259 } else { |
256 target.clear(Parameter.SOCK); | 260 // tcp |
257 target.setString(Parameter.HOST, host); | 261 target.clear(Parameter.SOCK); |
258 if (path == null || path.isEmpty()) { | 262 target.setString(Parameter.HOST, host); |
259 // do nothing | 263 if (path == null || path.isEmpty()) { |
260 } else if (!path.startsWith("/")) { | 264 // do nothing |
261 throw new URISyntaxException(urlText, "expect path to start with /"); | 265 } else if (!path.startsWith("/")) { |
262 } else { | 266 throw new URISyntaxException(urlText, "expect path to start with /"); |
263 String database = path.substring(1); | 267 } else { |
264 target.setString(Parameter.DATABASE, database); | 268 String database = path.substring(1); |
265 } | 269 target.setString(Parameter.DATABASE, database); |
266 } | 270 } |
267 } | 271 } |
268 | 272 } |
269 private void clearBasic() { | 273 |
270 target.clear(Parameter.TLS); | 274 private void clearBasic() { |
271 target.clear(Parameter.HOST); | 275 target.clear(Parameter.TLS); |
272 target.clear(Parameter.PORT); | 276 target.clear(Parameter.HOST); |
273 target.clear(Parameter.DATABASE); | 277 target.clear(Parameter.PORT); |
274 } | 278 target.clear(Parameter.DATABASE); |
279 } | |
275 } | 280 } |