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 public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
668 {
669 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
670 url.append(scheme).append("://").append('[').append(server).append(']');
671 else
672 url.append(scheme).append("://").append(server);
673
674 if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
675 url.append(':').append(port);
676 }
677
678 public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
679 {
680 synchronized (url)
681 {
682 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
683 url.append(scheme).append("://").append('[').append(server).append(']');
684 else
685 url.append(scheme).append("://").append(server);
686
687 if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
688 url.append(':').append(port);
689 }
690 }
691 }
692
693
694