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