1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.util;
20
21 import java.nio.charset.Charset;
22 import java.nio.charset.StandardCharsets;
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public class URIUtil
38 implements Cloneable
39 {
40 public static final String SLASH="/";
41 public static final String HTTP="http";
42 public static final String HTTP_COLON="http:";
43 public static final String HTTPS="https";
44 public static final String HTTPS_COLON="https:";
45
46
47 public static final Charset __CHARSET;
48
49 static
50 {
51 String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
52 __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
53 }
54
55 private URIUtil()
56 {}
57
58
59
60
61
62
63
64
65 public static String encodePath(String path)
66 {
67 if (path==null || path.length()==0)
68 return path;
69
70 StringBuilder buf = encodePath(null,path);
71 return buf==null?path:buf.toString();
72 }
73
74
75
76
77
78
79
80 public static StringBuilder encodePath(StringBuilder buf, String path)
81 {
82 byte[] bytes=null;
83 if (buf==null)
84 {
85 loop:
86 for (int i=0;i<path.length();i++)
87 {
88 char c=path.charAt(i);
89 switch(c)
90 {
91 case '%':
92 case '?':
93 case ';':
94 case '#':
95 case '\'':
96 case '"':
97 case '<':
98 case '>':
99 case ' ':
100 buf=new StringBuilder(path.length()*2);
101 break loop;
102 default:
103 if (c>127)
104 {
105 bytes=path.getBytes(URIUtil.__CHARSET);
106 buf=new StringBuilder(path.length()*2);
107 break loop;
108 }
109
110 }
111 }
112 if (buf==null)
113 return null;
114 }
115
116 synchronized(buf)
117 {
118 if (bytes!=null)
119 {
120 for (int i=0;i<bytes.length;i++)
121 {
122 byte c=bytes[i];
123 switch(c)
124 {
125 case '%':
126 buf.append("%25");
127 continue;
128 case '?':
129 buf.append("%3F");
130 continue;
131 case ';':
132 buf.append("%3B");
133 continue;
134 case '#':
135 buf.append("%23");
136 continue;
137 case '"':
138 buf.append("%22");
139 continue;
140 case '\'':
141 buf.append("%27");
142 continue;
143 case '<':
144 buf.append("%3C");
145 continue;
146 case '>':
147 buf.append("%3E");
148 continue;
149 case ' ':
150 buf.append("%20");
151 continue;
152 default:
153 if (c<0)
154 {
155 buf.append('%');
156 TypeUtil.toHex(c,buf);
157 }
158 else
159 buf.append((char)c);
160 continue;
161 }
162 }
163
164 }
165 else
166 {
167 for (int i=0;i<path.length();i++)
168 {
169 char c=path.charAt(i);
170 switch(c)
171 {
172 case '%':
173 buf.append("%25");
174 continue;
175 case '?':
176 buf.append("%3F");
177 continue;
178 case ';':
179 buf.append("%3B");
180 continue;
181 case '#':
182 buf.append("%23");
183 continue;
184 case '"':
185 buf.append("%22");
186 continue;
187 case '\'':
188 buf.append("%27");
189 continue;
190 case '<':
191 buf.append("%3C");
192 continue;
193 case '>':
194 buf.append("%3E");
195 continue;
196 case ' ':
197 buf.append("%20");
198 continue;
199 default:
200 buf.append(c);
201 continue;
202 }
203 }
204 }
205 }
206
207 return buf;
208 }
209
210
211
212
213
214
215
216
217 public static StringBuilder encodeString(StringBuilder buf,
218 String path,
219 String encode)
220 {
221 if (buf==null)
222 {
223 loop:
224 for (int i=0;i<path.length();i++)
225 {
226 char c=path.charAt(i);
227 if (c=='%' || encode.indexOf(c)>=0)
228 {
229 buf=new StringBuilder(path.length()<<1);
230 break loop;
231 }
232 }
233 if (buf==null)
234 return null;
235 }
236
237 synchronized(buf)
238 {
239 for (int i=0;i<path.length();i++)
240 {
241 char c=path.charAt(i);
242 if (c=='%' || encode.indexOf(c)>=0)
243 {
244 buf.append('%');
245 StringUtil.append(buf,(byte)(0xff&c),16);
246 }
247 else
248 buf.append(c);
249 }
250 }
251
252 return buf;
253 }
254
255
256
257
258
259
260 public static String decodePath(String path)
261 {
262 if (path==null)
263 return null;
264
265 char[] chars=null;
266 int n=0;
267
268 byte[] bytes=null;
269 int b=0;
270
271 int len=path.length();
272
273 for (int i=0;i<len;i++)
274 {
275 char c = path.charAt(i);
276
277 if (c=='%' && (i+2)<len)
278 {
279 if (chars==null)
280 {
281 chars=new char[len];
282 bytes=new byte[len];
283 path.getChars(0,i,chars,0);
284 }
285 bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
286 i+=2;
287 continue;
288 }
289 else if (c==';')
290 {
291 if (chars==null)
292 {
293 chars=new char[len];
294 path.getChars(0,i,chars,0);
295 n=i;
296 }
297 break;
298 }
299 else if (bytes==null)
300 {
301 n++;
302 continue;
303 }
304
305
306 if (b>0)
307 {
308 String s=new String(bytes,0,b,__CHARSET);
309 s.getChars(0,s.length(),chars,n);
310 n+=s.length();
311 b=0;
312 }
313
314 chars[n++]=c;
315 }
316
317 if (chars==null)
318 return path;
319
320
321 if (b>0)
322 {
323 String s=new String(bytes,0,b,__CHARSET);
324 s.getChars(0,s.length(),chars,n);
325 n+=s.length();
326 }
327
328 return new String(chars,0,n);
329 }
330
331
332
333
334
335
336 public static String decodePath(byte[] buf, int offset, int length)
337 {
338 byte[] bytes=null;
339 int n=0;
340
341 for (int i=0;i<length;i++)
342 {
343 byte b = buf[i + offset];
344
345 if (b=='%' && (i+2)<length)
346 {
347 b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
348 i+=2;
349 }
350 else if (b==';')
351 {
352 length=i;
353 break;
354 }
355 else if (bytes==null)
356 {
357 n++;
358 continue;
359 }
360
361 if (bytes==null)
362 {
363 bytes=new byte[length];
364 for (int j=0;j<n;j++)
365 bytes[j]=buf[j + offset];
366 }
367
368 bytes[n++]=b;
369 }
370
371 if (bytes==null)
372 return new String(buf,offset,length,__CHARSET);
373 return new String(bytes,0,n,__CHARSET);
374 }
375
376
377
378
379
380
381
382
383
384
385 public static String addPaths(String p1, String p2)
386 {
387 if (p1==null || p1.length()==0)
388 {
389 if (p1!=null && p2==null)
390 return p1;
391 return p2;
392 }
393 if (p2==null || p2.length()==0)
394 return p1;
395
396 int split=p1.indexOf(';');
397 if (split<0)
398 split=p1.indexOf('?');
399 if (split==0)
400 return p2+p1;
401 if (split<0)
402 split=p1.length();
403
404 StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
405 buf.append(p1);
406
407 if (buf.charAt(split-1)=='/')
408 {
409 if (p2.startsWith(URIUtil.SLASH))
410 {
411 buf.deleteCharAt(split-1);
412 buf.insert(split-1,p2);
413 }
414 else
415 buf.insert(split,p2);
416 }
417 else
418 {
419 if (p2.startsWith(URIUtil.SLASH))
420 buf.insert(split,p2);
421 else
422 {
423 buf.insert(split,'/');
424 buf.insert(split+1,p2);
425 }
426 }
427
428 return buf.toString();
429 }
430
431
432
433
434
435 public static String parentPath(String p)
436 {
437 if (p==null || URIUtil.SLASH.equals(p))
438 return null;
439 int slash=p.lastIndexOf('/',p.length()-2);
440 if (slash>=0)
441 return p.substring(0,slash+1);
442 return null;
443 }
444
445
446
447
448
449
450
451
452 public static String canonicalPath(String path)
453 {
454 if (path==null || path.length()==0)
455 return path;
456
457 int end=path.length();
458 int start = path.lastIndexOf('/', end);
459
460 search:
461 while (end>0)
462 {
463 switch(end-start)
464 {
465 case 2:
466 if (path.charAt(start+1)!='.')
467 break;
468 break search;
469 case 3:
470 if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
471 break;
472 break search;
473 }
474
475 end=start;
476 start=path.lastIndexOf('/',end-1);
477 }
478
479
480 if (start>=end)
481 return path;
482
483 StringBuilder buf = new StringBuilder(path);
484 int delStart=-1;
485 int delEnd=-1;
486 int skip=0;
487
488 while (end>0)
489 {
490 switch(end-start)
491 {
492 case 2:
493 if (buf.charAt(start+1)!='.')
494 {
495 if (skip>0 && --skip==0)
496 {
497 delStart=start>=0?start:0;
498 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
499 delStart++;
500 }
501 break;
502 }
503
504 if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
505 break;
506
507 if(delEnd<0)
508 delEnd=end;
509 delStart=start;
510 if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
511 {
512 delStart++;
513 if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
514 delEnd++;
515 break;
516 }
517 if (end==buf.length())
518 delStart++;
519
520 end=start--;
521 while (start>=0 && buf.charAt(start)!='/')
522 start--;
523 continue;
524
525 case 3:
526 if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
527 {
528 if (skip>0 && --skip==0)
529 { delStart=start>=0?start:0;
530 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
531 delStart++;
532 }
533 break;
534 }
535
536 delStart=start;
537 if (delEnd<0)
538 delEnd=end;
539
540 skip++;
541 end=start--;
542 while (start>=0 && buf.charAt(start)!='/')
543 start--;
544 continue;
545
546 default:
547 if (skip>0 && --skip==0)
548 {
549 delStart=start>=0?start:0;
550 if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
551 delStart++;
552 }
553 }
554
555
556 if (skip<=0 && delStart>=0 && delEnd>=delStart)
557 {
558 buf.delete(delStart,delEnd);
559 delStart=delEnd=-1;
560 if (skip>0)
561 delEnd=end;
562 }
563
564 end=start--;
565 while (start>=0 && buf.charAt(start)!='/')
566 start--;
567 }
568
569
570 if (skip>0)
571 return null;
572
573
574 if (delEnd>=0)
575 buf.delete(delStart,delEnd);
576
577 return buf.toString();
578 }
579
580
581
582
583
584
585
586 public static String compactPath(String path)
587 {
588 if (path==null || path.length()==0)
589 return path;
590
591 int state=0;
592 int end=path.length();
593 int i=0;
594
595 loop:
596 while (i<end)
597 {
598 char c=path.charAt(i);
599 switch(c)
600 {
601 case '?':
602 return path;
603 case '/':
604 state++;
605 if (state==2)
606 break loop;
607 break;
608 default:
609 state=0;
610 }
611 i++;
612 }
613
614 if (state<2)
615 return path;
616
617 StringBuffer buf = new StringBuffer(path.length());
618 buf.append(path,0,i);
619
620 loop2:
621 while (i<end)
622 {
623 char c=path.charAt(i);
624 switch(c)
625 {
626 case '?':
627 buf.append(path,i,end);
628 break loop2;
629 case '/':
630 if (state++==0)
631 buf.append(c);
632 break;
633 default:
634 state=0;
635 buf.append(c);
636 }
637 i++;
638 }
639
640 return buf.toString();
641 }
642
643
644
645
646
647
648 public static boolean hasScheme(String uri)
649 {
650 for (int i=0;i<uri.length();i++)
651 {
652 char c=uri.charAt(i);
653 if (c==':')
654 return true;
655 if (!(c>='a'&&c<='z' ||
656 c>='A'&&c<='Z' ||
657 (i>0 &&(c>='0'&&c<='9' ||
658 c=='.' ||
659 c=='+' ||
660 c=='-'))
661 ))
662 break;
663 }
664 return false;
665 }
666
667
668
669
670
671
672
673
674
675
676
677 public static String newURI(String scheme,String server, int port,String path,String query)
678 {
679 StringBuilder builder = newURIBuilder(scheme, server, port);
680 builder.append(path);
681 if (query!=null && query.length()>0)
682 builder.append('?').append(query);
683 return builder.toString();
684 }
685
686
687
688
689
690
691
692
693
694 public static StringBuilder newURIBuilder(String scheme,String server, int port)
695 {
696 StringBuilder builder = new StringBuilder();
697 appendSchemeHostPort(builder, scheme, server, port);
698 return builder;
699 }
700
701
702
703
704
705
706
707
708
709 public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
710 {
711 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
712 url.append(scheme).append("://").append('[').append(server).append(']');
713 else
714 url.append(scheme).append("://").append(server);
715
716 if (port > 0)
717 {
718 switch(scheme)
719 {
720 case "http":
721 if (port!=80)
722 url.append(':').append(port);
723 break;
724
725 case "https":
726 if (port!=443)
727 url.append(':').append(port);
728 break;
729
730 default:
731 url.append(':').append(port);
732 }
733 }
734 }
735
736
737
738
739
740
741
742
743
744 public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
745 {
746 synchronized (url)
747 {
748 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
749 url.append(scheme).append("://").append('[').append(server).append(']');
750 else
751 url.append(scheme).append("://").append(server);
752
753 if (port > 0)
754 {
755 switch(scheme)
756 {
757 case "http":
758 if (port!=80)
759 url.append(':').append(port);
760 break;
761
762 case "https":
763 if (port!=443)
764 url.append(':').append(port);
765 break;
766
767 default:
768 url.append(':').append(port);
769 }
770 }
771 }
772 }
773
774 public static boolean equalsIgnoreEncodings(String uriA, String uriB)
775 {
776 int lenA=uriA.length();
777 int lenB=uriB.length();
778 int a=0;
779 int b=0;
780
781 while (a<lenA && b<lenB)
782 {
783 int oa=uriA.charAt(a++);
784 int ca=oa;
785 if (ca=='%')
786 ca=TypeUtil.convertHexDigit(uriA.charAt(a++))*16+TypeUtil.convertHexDigit(uriA.charAt(a++));
787
788 int ob=uriB.charAt(b++);
789 int cb=ob;
790 if (cb=='%')
791 cb=TypeUtil.convertHexDigit(uriB.charAt(b++))*16+TypeUtil.convertHexDigit(uriB.charAt(b++));
792
793 if (ca=='/' && oa!=ob)
794 return false;
795
796 if (ca!=cb )
797 return URIUtil.decodePath(uriA).equals(URIUtil.decodePath(uriB));
798 }
799 return a==lenA && b==lenB;
800 }
801 }
802
803
804