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
209
210 public URIish(String s) throws URISyntaxException {
211 if (StringUtils.isEmptyOrNull(s)) {
212 throw new URISyntaxException("The uri was empty or null",
213 JGitText.get().cannotParseGitURIish);
214 }
215 Matcher matcher = SINGLE_SLASH_FILE_URI.matcher(s);
216 if (matcher.matches()) {
217 scheme = matcher.group(1);
218 rawPath = cleanLeadingSlashes(matcher.group(2), scheme);
219 path = unescape(rawPath);
220 return;
221 }
222 matcher = FULL_URI.matcher(s);
223 if (matcher.matches()) {
224 scheme = matcher.group(1);
225 user = unescape(matcher.group(2));
226 pass = unescape(matcher.group(3));
227
228
229
230 String portString = matcher.group(5);
231 if ("file".equals(scheme) && "".equals(portString)) {
232 rawPath = cleanLeadingSlashes(
233 n2e(matcher.group(4)) + ":" + portString
234 + n2e(matcher.group(6)) + n2e(matcher.group(7)),
235 scheme);
236 } else {
237 host = unescape(matcher.group(4));
238 if (portString != null && portString.length() > 0) {
239 port = Integer.parseInt(portString);
240 }
241 rawPath = cleanLeadingSlashes(
242 n2e(matcher.group(6)) + n2e(matcher.group(7)), scheme);
243 }
244 path = unescape(rawPath);
245 return;
246 }
247 matcher = RELATIVE_SCP_URI.matcher(s);
248 if (matcher.matches()) {
249 user = matcher.group(1);
250 pass = matcher.group(2);
251 host = matcher.group(3);
252 rawPath = matcher.group(4);
253 path = rawPath;
254 return;
255 }
256 matcher = ABSOLUTE_SCP_URI.matcher(s);
257 if (matcher.matches()) {
258 user = matcher.group(1);
259 pass = matcher.group(2);
260 host = matcher.group(3);
261 rawPath = matcher.group(4);
262 path = rawPath;
263 return;
264 }
265 matcher = LOCAL_FILE.matcher(s);
266 if (matcher.matches()) {
267 rawPath = matcher.group(1);
268 path = rawPath;
269 return;
270 }
271 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
272 }
273
274 private static int parseHexByte(byte c1, byte c2) {
275 return ((RawParseUtils.parseHexInt4(c1) << 4)
276 | RawParseUtils.parseHexInt4(c2));
277 }
278
279 private static String unescape(String s) throws URISyntaxException {
280 if (s == null)
281 return null;
282 if (s.indexOf('%') < 0)
283 return s;
284
285 byte[] bytes;
286 try {
287 bytes = s.getBytes(Constants.CHARACTER_ENCODING);
288 } catch (UnsupportedEncodingException e) {
289 throw new RuntimeException(e);
290 }
291
292 byte[] os = new byte[bytes.length];
293 int j = 0;
294 for (int i = 0; i < bytes.length; ++i) {
295 byte c = bytes[i];
296 if (c == '%') {
297 if (i + 2 >= bytes.length)
298 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
299 byte c1 = bytes[i + 1];
300 byte c2 = bytes[i + 2];
301 int val;
302 try {
303 val = parseHexByte(c1, c2);
304 } catch (ArrayIndexOutOfBoundsException e) {
305 throw new URISyntaxException(s, JGitText.get().cannotParseGitURIish);
306 }
307 os[j++] = (byte) val;
308 i += 2;
309 } else
310 os[j++] = c;
311 }
312 return RawParseUtils.decode(os, 0, j);
313 }
314
315 private static final BitSet reservedChars = new BitSet(127);
316
317 static {
318 for (byte b : Constants.encodeASCII("!*'();:@&=+$,/?#[]"))
319 reservedChars.set(b);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333 private static String escape(String s, boolean escapeReservedChars,
334 boolean encodeNonAscii) {
335 if (s == null)
336 return null;
337 ByteArrayOutputStream os = new ByteArrayOutputStream(s.length());
338 byte[] bytes;
339 try {
340 bytes = s.getBytes(Constants.CHARACTER_ENCODING);
341 } catch (UnsupportedEncodingException e) {
342 throw new RuntimeException(e);
343 }
344 for (int i = 0; i < bytes.length; ++i) {
345 int b = bytes[i] & 0xFF;
346 if (b <= 32 || (encodeNonAscii && b > 127) || b == '%'
347 || (escapeReservedChars && reservedChars.get(b))) {
348 os.write('%');
349 byte[] tmp = Constants.encodeASCII(String.format("%02x",
350 Integer.valueOf(b)));
351 os.write(tmp[0]);
352 os.write(tmp[1]);
353 } else {
354 os.write(b);
355 }
356 }
357 byte[] buf = os.toByteArray();
358 return RawParseUtils.decode(buf, 0, buf.length);
359 }
360
361 private String n2e(String s) {
362 if (s == null)
363 return "";
364 else
365 return s;
366 }
367
368
369
370 private String cleanLeadingSlashes(String p, String s) {
371 if (p.length() >= 3
372 && p.charAt(0) == '/'
373 && p.charAt(2) == ':'
374 && (p.charAt(1) >= 'A' && p.charAt(1) <= 'Z' || p.charAt(1) >= 'a'
375 && p.charAt(1) <= 'z'))
376 return p.substring(1);
377 else if (s != null && p.length() >= 2 && p.charAt(0) == '/'
378 && p.charAt(1) == '~')
379 return p.substring(1);
380 else
381 return p;
382 }
383
384
385
386
387
388
389
390 public URIish(URL u) {
391 scheme = u.getProtocol();
392 path = u.getPath();
393 path = cleanLeadingSlashes(path, scheme);
394 try {
395 rawPath = u.toURI().getRawPath();
396 rawPath = cleanLeadingSlashes(rawPath, scheme);
397 } catch (URISyntaxException e) {
398 throw new RuntimeException(e);
399 }
400
401 final String ui = u.getUserInfo();
402 if (ui != null) {
403 final int d = ui.indexOf(':');
404 user = d < 0 ? ui : ui.substring(0, d);
405 pass = d < 0 ? null : ui.substring(d + 1);
406 }
407
408 port = u.getPort();
409 host = u.getHost();
410 }
411
412
413
414
415 public URIish() {
416
417 }
418
419 private URIish(URIish u) {
420 this.scheme = u.scheme;
421 this.rawPath = u.rawPath;
422 this.path = u.path;
423 this.user = u.user;
424 this.pass = u.pass;
425 this.port = u.port;
426 this.host = u.host;
427 }
428
429
430
431
432
433
434 public boolean isRemote() {
435 return getHost() != null;
436 }
437
438
439
440
441
442
443 public String getHost() {
444 return host;
445 }
446
447
448
449
450
451
452
453
454 public URIish setHost(String n) {
455 final URIish r = new URIish(this);
456 r.host = n;
457 return r;
458 }
459
460
461
462
463
464
465 public String getScheme() {
466 return scheme;
467 }
468
469
470
471
472
473
474
475
476 public URIish setScheme(String n) {
477 final URIish r = new URIish(this);
478 r.scheme = n;
479 return r;
480 }
481
482
483
484
485
486
487 public String getPath() {
488 return path;
489 }
490
491
492
493
494
495
496 public String getRawPath() {
497 return rawPath;
498 }
499
500
501
502
503
504
505
506
507 public URIish setPath(String n) {
508 final URIish r = new URIish(this);
509 r.path = n;
510 r.rawPath = n;
511 return r;
512 }
513
514
515
516
517
518
519
520
521
522 public URIish setRawPath(String n) throws URISyntaxException {
523 final URIish r = new URIish(this);
524 r.path = unescape(n);
525 r.rawPath = n;
526 return r;
527 }
528
529
530
531
532
533
534 public String getUser() {
535 return user;
536 }
537
538
539
540
541
542
543
544
545 public URIish setUser(String n) {
546 final URIish r = new URIish(this);
547 r.user = n;
548 return r;
549 }
550
551
552
553
554
555
556 public String getPass() {
557 return pass;
558 }
559
560
561
562
563
564
565
566
567 public URIish setPass(String n) {
568 final URIish r = new URIish(this);
569 r.pass = n;
570 return r;
571 }
572
573
574
575
576
577
578 public int getPort() {
579 return port;
580 }
581
582
583
584
585
586
587
588
589 public URIish setPort(int n) {
590 final URIish r = new URIish(this);
591 r.port = n > 0 ? n : -1;
592 return r;
593 }
594
595
596 @Override
597 public int hashCode() {
598 int hc = 0;
599 if (getScheme() != null)
600 hc = hc * 31 + getScheme().hashCode();
601 if (getUser() != null)
602 hc = hc * 31 + getUser().hashCode();
603 if (getPass() != null)
604 hc = hc * 31 + getPass().hashCode();
605 if (getHost() != null)
606 hc = hc * 31 + getHost().hashCode();
607 if (getPort() > 0)
608 hc = hc * 31 + getPort();
609 if (getPath() != null)
610 hc = hc * 31 + getPath().hashCode();
611 return hc;
612 }
613
614
615 @Override
616 public boolean equals(Object obj) {
617 if (!(obj instanceof URIish))
618 return false;
619 final URIish b = (URIish) obj;
620 if (!eq(getScheme(), b.getScheme()))
621 return false;
622 if (!eq(getUser(), b.getUser()))
623 return false;
624 if (!eq(getPass(), b.getPass()))
625 return false;
626 if (!eq(getHost(), b.getHost()))
627 return false;
628 if (getPort() != b.getPort())
629 return false;
630 if (!eq(getPath(), b.getPath()))
631 return false;
632 return true;
633 }
634
635 private static boolean eq(String a, String b) {
636 if (a == b)
637 return true;
638 if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b))
639 return true;
640 if (a == null || b == null)
641 return false;
642 return a.equals(b);
643 }
644
645
646
647
648
649
650 public String toPrivateString() {
651 return format(true, false);
652 }
653
654
655 @Override
656 public String toString() {
657 return format(false, false);
658 }
659
660 private String format(boolean includePassword, boolean escapeNonAscii) {
661 final StringBuilder r = new StringBuilder();
662 if (getScheme() != null) {
663 r.append(getScheme());
664 r.append("://"); //$NON-NLS-1$
665 }
666
667 if (getUser() != null) {
668 r.append(escape(getUser(), true, escapeNonAscii));
669 if (includePassword && getPass() != null) {
670 r.append(':');
671 r.append(escape(getPass(), true, escapeNonAscii));
672 }
673 }
674
675 if (getHost() != null) {
676 if (getUser() != null && getUser().length() > 0)
677 r.append('@');
678 r.append(escape(getHost(), false, escapeNonAscii));
679 if (getScheme() != null && getPort() > 0) {
680 r.append(':');
681 r.append(getPort());
682 }
683 }
684
685 if (getPath() != null) {
686 if (getScheme() != null) {
687 if (!getPath().startsWith("/") && !getPath().isEmpty())
688 r.append('/');
689 } else if (getHost() != null)
690 r.append(':');
691 if (getScheme() != null)
692 if (escapeNonAscii)
693 r.append(escape(getPath(), false, escapeNonAscii));
694 else
695 r.append(getRawPath());
696 else
697 r.append(getPath());
698 }
699
700 return r.toString();
701 }
702
703
704
705
706
707
708 public String toASCIIString() {
709 return format(false, true);
710 }
711
712
713
714
715
716
717
718
719 public String toPrivateASCIIString() {
720 return format(true, true);
721 }
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761 public String getHumanishName() throws IllegalArgumentException {
762 String s = getPath();
763 if ("/".equals(s) || "".equals(s))
764 s = getHost();
765 if (s == null)
766 throw new IllegalArgumentException();
767
768 String[] elements;
769 if ("file".equals(scheme) || LOCAL_FILE.matcher(s).matches())
770 elements = s.split("[\\" + File.separatorChar + "/]");
771 else
772 elements = s.split("/+");
773 if (elements.length == 0)
774 throw new IllegalArgumentException();
775 String result = elements[elements.length - 1];
776 if (Constants.DOT_GIT.equals(result))
777 result = elements[elements.length - 2];
778 else if (result.endsWith(Constants.DOT_GIT_EXT))
779 result = result.substring(0, result.length()
780 - Constants.DOT_GIT_EXT.length());
781 return result;
782 }
783
784 }