1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 package org.eclipse.jgit.transport;
50
51 import java.io.ByteArrayOutputStream;
52 import java.io.File;
53 import java.io.Serializable;
54 import java.io.UnsupportedEncodingException;
55 import java.net.URISyntaxException;
56 import java.net.URL;
57 import java.util.BitSet;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
60
61 import org.eclipse.jgit.internal.JGitText;
62 import org.eclipse.jgit.lib.Constants;
63 import org.eclipse.jgit.util.RawParseUtils;
64 import org.eclipse.jgit.util.StringUtils;
65
66
67
68
69
70
71
72 public class URIish implements Serializable {
73
74
75
76
77
78 private static final String SCHEME_P = "([a-z][a-z0-9+-]+)://"; //$NON-NLS-1$
79
80
81
82
83
84
85
86 private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?";
87
88
89
90
91
92 private static final String HOST_P = "((?:[^\\\\/:]+)|(?:\\[[0-9a-f:]+\\]))";
93
94
95
96
97
98 private static final String OPT_PORT_P = "(?::(\\d+))?";
99
100
101
102
103
104 private static final String USER_HOME_P = "(?:/~(?:[^\\\\/]+))";
105
106
107
108
109
110 private static final String OPT_DRIVE_LETTER_P = "(?:[A-Za-z]:)?";
111
112
113
114
115
116 private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*[^\\\\/]+[\\\\/]*)";
117
118
119
120
121
122 private static final String PATH_P = "(" + OPT_DRIVE_LETTER_P + "[\\\\/]?"
123 + RELATIVE_PATH_P + ")";
124
125 private static final long serialVersionUID = 1L;
126
127
128
129
130
131 private static final Pattern FULL_URI = Pattern.compile("^"
132 + SCHEME_P
133 + "(?:"
134
135 + OPT_USER_PWD_P
136 + HOST_P
137 + OPT_PORT_P
138 + "("
139 + (USER_HOME_P + "?")
140 + "(?:"
141
142 + "[\\\\/])|$"
143 + ")"
144
145 + ")?"
146 + "(.+)?"
147 + "$");
148
149
150
151
152
153 private static final Pattern LOCAL_FILE = Pattern.compile("^"
154 + "([\\\\/]?" + PATH_P + ")"
155 + "$");
156
157
158
159
160
161
162 private static final Pattern SINGLE_SLASH_FILE_URI = Pattern.compile("^"
163 + "(file):([\\\\/](?![\\\\/])"
164 + PATH_P
165 + ")$");
166
167
168
169
170 private static final Pattern RELATIVE_SCP_URI = Pattern.compile("^"
171 + OPT_USER_PWD_P
172 + HOST_P
173 + ":("
174 + ("(?:" + USER_HOME_P + "[\\\\/])?")
175 + RELATIVE_PATH_P
176 + ")$");
177
178
179
180
181 private static final Pattern ABSOLUTE_SCP_URI = Pattern.compile("^"
182 + OPT_USER_PWD_P
183 + "([^\\\\/:]{2,})"
184 + ":("
185 + "[\\\\/]" + RELATIVE_PATH_P
186 + ")$");
187
188 private String scheme;
189
190 private String path;
191
192 private String rawPath;
193
194 private String user;
195
196 private String pass;
197
198 private int port = -1;
199
200 private String host;
201
202
203
204
205
206
207
208 public URIish(String s) throws URISyntaxException {
209 if (StringUtils.isEmptyOrNull(s)) {
210 throw new URISyntaxException("The uri was empty or null",
211 JGitText.get().cannotParseGitURIish);
212 }
213 Matcher matcher = SINGLE_SLASH_FILE_URI.matcher(s);
214 if (matcher.matches()) {
215 scheme = matcher.group(1);
216 rawPath = cleanLeadingSlashes(matcher.group(2), scheme);
217 path = unescape(rawPath);
218 return;
219 }
220 matcher = FULL_URI.matcher(s);
221 if (matcher.matches()) {
222 scheme = matcher.group(1);
223 user = unescape(matcher.group(2));
224 pass = unescape(matcher.group(3));
225 host = unescape(matcher.group(4));
226 if (matcher.group(5) != null)
227 port = Integer.parseInt(matcher.group(5));
228 rawPath = cleanLeadingSlashes(
229 n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme);
230 path = unescape(rawPath);
231 return;
232 }
233 matcher = RELATIVE_SCP_URI.matcher(s);
234 if (matcher.matches()) {
235 user = matcher.group(1);
236 pass = matcher.group(2);
237 host = matcher.group(3);
238 rawPath = matcher.group(4);
239 path = rawPath;
240 return;
241 }
242 matcher = ABSOLUTE_SCP_URI.matcher(s);
243 if (matcher.matches()) {
244 user = matcher.group(1);
245 pass = matcher.group(2);
246 host = matcher.group(3);
247 rawPath = matcher.group(4);
248 path = rawPath;
249 return;
250 }
251 matcher = LOCAL_FILE.matcher(s);
252 if (matcher.matches()) {
253 rawPath = matcher.group(1);
254 path = rawPath;
255 return;
256 }
257 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
258 }
259
260 private static int parseHexByte(byte c1, byte c2) {
261 return ((RawParseUtils.parseHexInt4(c1) << 4)
262 | RawParseUtils.parseHexInt4(c2));
263 }
264
265 private static String unescape(String s) throws URISyntaxException {
266 if (s == null)
267 return null;
268 if (s.indexOf('%') < 0)
269 return s;
270
271 byte[] bytes;
272 try {
273 bytes = s.getBytes(Constants.CHARACTER_ENCODING);
274 } catch (UnsupportedEncodingException e) {
275 throw new RuntimeException(e);
276 }
277
278 byte[] os = new byte[bytes.length];
279 int j = 0;
280 for (int i = 0; i < bytes.length; ++i) {
281 byte c = bytes[i];
282 if (c == '%') {
283 if (i + 2 >= bytes.length)
284 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
285 byte c1 = bytes[i + 1];
286 byte c2 = bytes[i + 2];
287 int val;
288 try {
289 val = parseHexByte(c1, c2);
290 } catch (ArrayIndexOutOfBoundsException e) {
291 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
292 }
293 os[j++] = (byte) val;
294 i += 2;
295 } else
296 os[j++] = c;
297 }
298 return RawParseUtils.decode(os, 0, j);
299 }
300
301 private static final BitSet reservedChars = new BitSet(127);
302
303 static {
304 for (byte b : Constants.encodeASCII("!*'();:@&=+$,/?#[]"))
305 reservedChars.set(b);
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319 private static String escape(String s, boolean escapeReservedChars,
320 boolean encodeNonAscii) {
321 if (s == null)
322 return null;
323 ByteArrayOutputStream os = new ByteArrayOutputStream(s.length());
324 byte[] bytes;
325 try {
326 bytes = s.getBytes(Constants.CHARACTER_ENCODING);
327 } catch (UnsupportedEncodingException e) {
328 throw new RuntimeException(e);
329 }
330 for (int i = 0; i < bytes.length; ++i) {
331 int b = bytes[i] & 0xFF;
332 if (b <= 32 || (encodeNonAscii && b > 127) || b == '%'
333 || (escapeReservedChars && reservedChars.get(b))) {
334 os.write('%');
335 byte[] tmp = Constants.encodeASCII(String.format("%02x",
336 Integer.valueOf(b)));
337 os.write(tmp[0]);
338 os.write(tmp[1]);
339 } else {
340 os.write(b);
341 }
342 }
343 byte[] buf = os.toByteArray();
344 return RawParseUtils.decode(buf, 0, buf.length);
345 }
346
347 private String n2e(String s) {
348 if (s == null)
349 return "";
350 else
351 return s;
352 }
353
354
355
356 private String cleanLeadingSlashes(String p, String s) {
357 if (p.length() >= 3
358 && p.charAt(0) == '/'
359 && p.charAt(2) == ':'
360 && (p.charAt(1) >= 'A' && p.charAt(1) <= 'Z' || p.charAt(1) >= 'a'
361 && p.charAt(1) <= 'z'))
362 return p.substring(1);
363 else if (s != null && p.length() >= 2 && p.charAt(0) == '/'
364 && p.charAt(1) == '~')
365 return p.substring(1);
366 else
367 return p;
368 }
369
370
371
372
373
374
375
376 public URIish(final URL u) {
377 scheme = u.getProtocol();
378 path = u.getPath();
379 path = cleanLeadingSlashes(path, scheme);
380 try {
381 rawPath = u.toURI().getRawPath();
382 rawPath = cleanLeadingSlashes(rawPath, scheme);
383 } catch (URISyntaxException e) {
384 throw new RuntimeException(e);
385 }
386
387 final String ui = u.getUserInfo();
388 if (ui != null) {
389 final int d = ui.indexOf(':');
390 user = d < 0 ? ui : ui.substring(0, d);
391 pass = d < 0 ? null : ui.substring(d + 1);
392 }
393
394 port = u.getPort();
395 host = u.getHost();
396 }
397
398
399 public URIish() {
400
401 }
402
403 private URIish(final URIish u) {
404 this.scheme = u.scheme;
405 this.rawPath = u.rawPath;
406 this.path = u.path;
407 this.user = u.user;
408 this.pass = u.pass;
409 this.port = u.port;
410 this.host = u.host;
411 }
412
413
414
415
416 public boolean isRemote() {
417 return getHost() != null;
418 }
419
420
421
422
423 public String getHost() {
424 return host;
425 }
426
427
428
429
430
431
432
433
434 public URIish setHost(final String n) {
435 final URIish r = new URIish(this);
436 r.host = n;
437 return r;
438 }
439
440
441
442
443 public String getScheme() {
444 return scheme;
445 }
446
447
448
449
450
451
452
453
454 public URIish setScheme(final String n) {
455 final URIish r = new URIish(this);
456 r.scheme = n;
457 return r;
458 }
459
460
461
462
463 public String getPath() {
464 return path;
465 }
466
467
468
469
470 public String getRawPath() {
471 return rawPath;
472 }
473
474
475
476
477
478
479
480
481 public URIish setPath(final String n) {
482 final URIish r = new URIish(this);
483 r.path = n;
484 r.rawPath = n;
485 return r;
486 }
487
488
489
490
491
492
493
494
495
496 public URIish setRawPath(final String n) throws URISyntaxException {
497 final URIish r = new URIish(this);
498 r.path = unescape(n);
499 r.rawPath = n;
500 return r;
501 }
502
503
504
505
506 public String getUser() {
507 return user;
508 }
509
510
511
512
513
514
515
516
517 public URIish setUser(final String n) {
518 final URIish r = new URIish(this);
519 r.user = n;
520 return r;
521 }
522
523
524
525
526 public String getPass() {
527 return pass;
528 }
529
530
531
532
533
534
535
536
537 public URIish setPass(final String n) {
538 final URIish r = new URIish(this);
539 r.pass = n;
540 return r;
541 }
542
543
544
545
546 public int getPort() {
547 return port;
548 }
549
550
551
552
553
554
555
556
557 public URIish setPort(final int n) {
558 final URIish r = new URIish(this);
559 r.port = n > 0 ? n : -1;
560 return r;
561 }
562
563 @Override
564 public int hashCode() {
565 int hc = 0;
566 if (getScheme() != null)
567 hc = hc * 31 + getScheme().hashCode();
568 if (getUser() != null)
569 hc = hc * 31 + getUser().hashCode();
570 if (getPass() != null)
571 hc = hc * 31 + getPass().hashCode();
572 if (getHost() != null)
573 hc = hc * 31 + getHost().hashCode();
574 if (getPort() > 0)
575 hc = hc * 31 + getPort();
576 if (getPath() != null)
577 hc = hc * 31 + getPath().hashCode();
578 return hc;
579 }
580
581 @Override
582 public boolean equals(final Object obj) {
583 if (!(obj instanceof URIish))
584 return false;
585 final URIish b = (URIish) obj;
586 if (!eq(getScheme(), b.getScheme()))
587 return false;
588 if (!eq(getUser(), b.getUser()))
589 return false;
590 if (!eq(getPass(), b.getPass()))
591 return false;
592 if (!eq(getHost(), b.getHost()))
593 return false;
594 if (getPort() != b.getPort())
595 return false;
596 if (!eq(getPath(), b.getPath()))
597 return false;
598 return true;
599 }
600
601 private static boolean eq(final String a, final String b) {
602 if (a == b)
603 return true;
604 if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b))
605 return true;
606 if (a == null || b == null)
607 return false;
608 return a.equals(b);
609 }
610
611
612
613
614
615
616 public String toPrivateString() {
617 return format(true, false);
618 }
619
620 @Override
621 public String toString() {
622 return format(false, false);
623 }
624
625 private String format(final boolean includePassword, boolean escapeNonAscii) {
626 final StringBuilder r = new StringBuilder();
627 if (getScheme() != null) {
628 r.append(getScheme());
629 r.append("://"); //$NON-NLS-1$
630 }
631
632 if (getUser() != null) {
633 r.append(escape(getUser(), true, escapeNonAscii));
634 if (includePassword && getPass() != null) {
635 r.append(':');
636 r.append(escape(getPass(), true, escapeNonAscii));
637 }
638 }
639
640 if (getHost() != null) {
641 if (getUser() != null && getUser().length() > 0)
642 r.append('@');
643 r.append(escape(getHost(), false, escapeNonAscii));
644 if (getScheme() != null && getPort() > 0) {
645 r.append(':');
646 r.append(getPort());
647 }
648 }
649
650 if (getPath() != null) {
651 if (getScheme() != null) {
652 if (!getPath().startsWith("/") && !getPath().isEmpty())
653 r.append('/');
654 } else if (getHost() != null)
655 r.append(':');
656 if (getScheme() != null)
657 if (escapeNonAscii)
658 r.append(escape(getPath(), false, escapeNonAscii));
659 else
660 r.append(getRawPath());
661 else
662 r.append(getPath());
663 }
664
665 return r.toString();
666 }
667
668
669
670
671 public String toASCIIString() {
672 return format(false, true);
673 }
674
675
676
677
678
679 public String toPrivateASCIIString() {
680 return format(true, true);
681 }
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721 public String getHumanishName() throws IllegalArgumentException {
722 String s = getPath();
723 if ("/".equals(s) || "".equals(s))
724 s = getHost();
725 if (s == null)
726 throw new IllegalArgumentException();
727
728 String[] elements;
729 if ("file".equals(scheme) || LOCAL_FILE.matcher(s).matches())
730 elements = s.split("[\\" + File.separatorChar + "/]");
731 else
732 elements = s.split("/+");
733 if (elements.length == 0)
734 throw new IllegalArgumentException();
735 String result = elements[elements.length - 1];
736 if (Constants.DOT_GIT.equals(result))
737 result = elements[elements.length - 2];
738 else if (result.endsWith(Constants.DOT_GIT_EXT))
739 result = result.substring(0, result.length()
740 - Constants.DOT_GIT_EXT.length());
741 return result;
742 }
743
744 }