comparison src/org/eclipse/jetty/util/URIUtil.java @ 1013:6939226e0ac4

simplify URIUtil
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 24 Oct 2016 01:06:33 -0600
parents 8e9db0bbf4f9
children
comparison
equal deleted inserted replaced
1012:8d0bdd357e6e 1013:6939226e0ac4
33 * <P>UTF-8 encoding is used by default for % encoded characters. This 33 * <P>UTF-8 encoding is used by default for % encoded characters. This
34 * may be overridden with the org.eclipse.jetty.util.URI.charset system property. 34 * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
35 * @see UrlEncoded 35 * @see UrlEncoded
36 * 36 *
37 */ 37 */
38 public class URIUtil 38 public final class URIUtil {
39 implements Cloneable 39 // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
40 { 40 public static final String __CHARSET = StringUtil.__UTF8;
41 public static final String SLASH="/"; 41
42 public static final String HTTP="http"; 42 private URIUtil()
43 public static final String HTTP_COLON="http:"; 43 {}
44 public static final String HTTPS="https"; 44
45 public static final String HTTPS_COLON="https:"; 45 /* ------------------------------------------------------------ */
46 46 /** Encode a URI path.
47 // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars 47 * This is the same encoding offered by URLEncoder, except that
48 public static final String __CHARSET=System.getProperty("org.eclipse.jetty.util.URI.charset",StringUtil.__UTF8); 48 * the '/' character is not encoded.
49 49 * @param path The path the encode
50 private URIUtil() 50 * @return The encoded path
51 {} 51 */
52 52 public static String encodePath(String path)
53 /* ------------------------------------------------------------ */ 53 {
54 /** Encode a URI path. 54 if (path==null || path.length()==0)
55 * This is the same encoding offered by URLEncoder, except that 55 return path;
56 * the '/' character is not encoded. 56
57 * @param path The path the encode 57 StringBuilder buf = encodePath(null,path);
58 * @return The encoded path 58 return buf==null?path:buf.toString();
59 */ 59 }
60 public static String encodePath(String path) 60
61 { 61 /* ------------------------------------------------------------ */
62 if (path==null || path.length()==0) 62 /** Encode a URI path.
63 return path; 63 * @param path The path the encode
64 64 * @param buf StringBuilder to encode path into (or null)
65 StringBuilder buf = encodePath(null,path); 65 * @return The StringBuilder or null if no substitutions required.
66 return buf==null?path:buf.toString(); 66 */
67 } 67 public static StringBuilder encodePath(StringBuilder buf, String path)
68 68 {
69 /* ------------------------------------------------------------ */ 69 byte[] bytes=null;
70 /** Encode a URI path. 70 if (buf==null)
71 * @param path The path the encode 71 {
72 * @param buf StringBuilder to encode path into (or null) 72 loop:
73 * @return The StringBuilder or null if no substitutions required. 73 for (int i=0;i<path.length();i++)
74 */ 74 {
75 public static StringBuilder encodePath(StringBuilder buf, String path) 75 char c=path.charAt(i);
76 { 76 switch(c)
77 byte[] bytes=null; 77 {
78 if (buf==null) 78 case '%':
79 { 79 case '?':
80 loop: 80 case ';':
81 for (int i=0;i<path.length();i++) 81 case '#':
82 { 82 case '\'':
83 char c=path.charAt(i); 83 case '"':
84 switch(c) 84 case '<':
85 { 85 case '>':
86 case '%': 86 case ' ':
87 case '?': 87 buf=new StringBuilder(path.length()*2);
88 case ';': 88 break loop;
89 case '#': 89 default:
90 case '\'': 90 if (c>127)
91 case '"': 91 {
92 case '<': 92 try
93 case '>': 93 {
94 case ' ': 94 bytes=path.getBytes(URIUtil.__CHARSET);
95 buf=new StringBuilder(path.length()*2); 95 }
96 break loop; 96 catch (UnsupportedEncodingException e)
97 default: 97 {
98 if (c>127) 98 throw new IllegalStateException(e);
99 { 99 }
100 try 100 buf=new StringBuilder(path.length()*2);
101 { 101 break loop;
102 bytes=path.getBytes(URIUtil.__CHARSET); 102 }
103 } 103
104 catch (UnsupportedEncodingException e) 104 }
105 { 105 }
106 throw new IllegalStateException(e); 106 if (buf==null)
107 } 107 return null;
108 buf=new StringBuilder(path.length()*2); 108 }
109 break loop; 109
110 } 110 synchronized(buf)
111 111 {
112 } 112 if (bytes!=null)
113 } 113 {
114 if (buf==null) 114 for (int i=0;i<bytes.length;i++)
115 return null; 115 {
116 } 116 byte c=bytes[i];
117 117 switch(c)
118 synchronized(buf) 118 {
119 { 119 case '%':
120 if (bytes!=null) 120 buf.append("%25");
121 { 121 continue;
122 for (int i=0;i<bytes.length;i++) 122 case '?':
123 { 123 buf.append("%3F");
124 byte c=bytes[i]; 124 continue;
125 switch(c) 125 case ';':
126 { 126 buf.append("%3B");
127 case '%': 127 continue;
128 buf.append("%25"); 128 case '#':
129 continue; 129 buf.append("%23");
130 case '?': 130 continue;
131 buf.append("%3F"); 131 case '"':
132 continue; 132 buf.append("%22");
133 case ';': 133 continue;
134 buf.append("%3B"); 134 case '\'':
135 continue; 135 buf.append("%27");
136 case '#': 136 continue;
137 buf.append("%23"); 137 case '<':
138 continue; 138 buf.append("%3C");
139 case '"': 139 continue;
140 buf.append("%22"); 140 case '>':
141 continue; 141 buf.append("%3E");
142 case '\'': 142 continue;
143 buf.append("%27"); 143 case ' ':
144 continue; 144 buf.append("%20");
145 case '<': 145 continue;
146 buf.append("%3C"); 146 default:
147 continue; 147 if (c<0)
148 case '>': 148 {
149 buf.append("%3E"); 149 buf.append('%');
150 continue; 150 TypeUtil.toHex(c,buf);
151 case ' ': 151 }
152 buf.append("%20"); 152 else
153 continue; 153 buf.append((char)c);
154 default: 154 continue;
155 if (c<0) 155 }
156 { 156 }
157 buf.append('%'); 157
158 TypeUtil.toHex(c,buf); 158 }
159 } 159 else
160 else 160 {
161 buf.append((char)c); 161 for (int i=0;i<path.length();i++)
162 continue; 162 {
163 } 163 char c=path.charAt(i);
164 } 164 switch(c)
165 165 {
166 } 166 case '%':
167 else 167 buf.append("%25");
168 { 168 continue;
169 for (int i=0;i<path.length();i++) 169 case '?':
170 { 170 buf.append("%3F");
171 char c=path.charAt(i); 171 continue;
172 switch(c) 172 case ';':
173 { 173 buf.append("%3B");
174 case '%': 174 continue;
175 buf.append("%25"); 175 case '#':
176 continue; 176 buf.append("%23");
177 case '?': 177 continue;
178 buf.append("%3F"); 178 case '"':
179 continue; 179 buf.append("%22");
180 case ';': 180 continue;
181 buf.append("%3B"); 181 case '\'':
182 continue; 182 buf.append("%27");
183 case '#': 183 continue;
184 buf.append("%23"); 184 case '<':
185 continue; 185 buf.append("%3C");
186 case '"': 186 continue;
187 buf.append("%22"); 187 case '>':
188 continue; 188 buf.append("%3E");
189 case '\'': 189 continue;
190 buf.append("%27"); 190 case ' ':
191 continue; 191 buf.append("%20");
192 case '<': 192 continue;
193 buf.append("%3C"); 193 default:
194 continue; 194 buf.append(c);
195 case '>': 195 continue;
196 buf.append("%3E"); 196 }
197 continue; 197 }
198 case ' ': 198 }
199 buf.append("%20"); 199 }
200 continue; 200
201 default: 201 return buf;
202 buf.append(c); 202 }
203 continue; 203
204 } 204 /* ------------------------------------------------------------ */
205 } 205 /* Decode a URI path and strip parameters
206 } 206 * @param path The path the encode
207 } 207 * @param buf StringBuilder to encode path into
208 208 */
209 return buf; 209 public static String decodePath(String path)
210 } 210 {
211 211 if (path==null)
212 /* ------------------------------------------------------------ */ 212 return null;
213 /** Encode a URI path. 213 // Array to hold all converted characters
214 * @param path The path the encode 214 char[] chars=null;
215 * @param buf StringBuilder to encode path into (or null) 215 int n=0;
216 * @param encode String of characters to encode. % is always encoded. 216 // Array to hold a sequence of %encodings
217 * @return The StringBuilder or null if no substitutions required. 217 byte[] bytes=null;
218 */ 218 int b=0;
219 public static StringBuilder encodeString(StringBuilder buf, 219
220 String path, 220 int len=path.length();
221 String encode) 221
222 { 222 for (int i=0;i<len;i++)
223 if (buf==null) 223 {
224 { 224 char c = path.charAt(i);
225 loop: 225
226 for (int i=0;i<path.length();i++) 226 if (c=='%' && (i+2)<len)
227 { 227 {
228 char c=path.charAt(i); 228 if (chars==null)
229 if (c=='%' || encode.indexOf(c)>=0) 229 {
230 { 230 chars=new char[len];
231 buf=new StringBuilder(path.length()<<1); 231 bytes=new byte[len];
232 break loop; 232 path.getChars(0,i,chars,0);
233 } 233 }
234 } 234 bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
235 if (buf==null) 235 i+=2;
236 return null; 236 continue;
237 } 237 }
238 238 else if (c==';')
239 synchronized(buf) 239 {
240 { 240 if (chars==null)
241 for (int i=0;i<path.length();i++) 241 {
242 { 242 chars=new char[len];
243 char c=path.charAt(i); 243 path.getChars(0,i,chars,0);
244 if (c=='%' || encode.indexOf(c)>=0) 244 n=i;
245 { 245 }
246 buf.append('%'); 246 break;
247 StringUtil.append(buf,(byte)(0xff&c),16); 247 }
248 } 248 else if (bytes==null)
249 else 249 {
250 buf.append(c); 250 n++;
251 } 251 continue;
252 } 252 }
253 253
254 return buf; 254 // Do we have some bytes to convert?
255 } 255 if (b>0)
256 256 {
257 /* ------------------------------------------------------------ */ 257 // convert series of bytes and add to chars
258 /* Decode a URI path and strip parameters 258 String s;
259 * @param path The path the encode 259 try
260 * @param buf StringBuilder to encode path into 260 {
261 */ 261 s=new String(bytes,0,b,__CHARSET);
262 public static String decodePath(String path) 262 }
263 { 263 catch (UnsupportedEncodingException e)
264 if (path==null) 264 {
265 return null; 265 s=new String(bytes,0,b);
266 // Array to hold all converted characters 266 }
267 char[] chars=null; 267 s.getChars(0,s.length(),chars,n);
268 int n=0; 268 n+=s.length();
269 // Array to hold a sequence of %encodings 269 b=0;
270 byte[] bytes=null; 270 }
271 int b=0; 271
272 272 chars[n++]=c;
273 int len=path.length(); 273 }
274 274
275 for (int i=0;i<len;i++) 275 if (chars==null)
276 { 276 return path;
277 char c = path.charAt(i); 277
278 278 // if we have a remaining sequence of bytes
279 if (c=='%' && (i+2)<len) 279 if (b>0)
280 { 280 {
281 if (chars==null) 281 // convert series of bytes and add to chars
282 { 282 String s;
283 chars=new char[len]; 283 try
284 bytes=new byte[len]; 284 {
285 path.getChars(0,i,chars,0); 285 s=new String(bytes,0,b,__CHARSET);
286 } 286 }
287 bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16)); 287 catch (UnsupportedEncodingException e)
288 i+=2; 288 {
289 continue; 289 s=new String(bytes,0,b);
290 } 290 }
291 else if (c==';') 291 s.getChars(0,s.length(),chars,n);
292 { 292 n+=s.length();
293 if (chars==null) 293 }
294 { 294
295 chars=new char[len]; 295 return new String(chars,0,n);
296 path.getChars(0,i,chars,0); 296 }
297 n=i; 297
298 } 298 /* ------------------------------------------------------------ */
299 break; 299 /* Decode a URI path and strip parameters.
300 } 300 * @param path The path the encode
301 else if (bytes==null) 301 * @param buf StringBuilder to encode path into
302 { 302 */
303 n++; 303 public static String decodePath(byte[] buf, int offset, int length)
304 continue; 304 {
305 } 305 byte[] bytes=null;
306 306 int n=0;
307 // Do we have some bytes to convert? 307
308 if (b>0) 308 for (int i=0;i<length;i++)
309 { 309 {
310 // convert series of bytes and add to chars 310 byte b = buf[i + offset];
311 String s; 311
312 try 312 if (b=='%' && (i+2)<length)
313 { 313 {
314 s=new String(bytes,0,b,__CHARSET); 314 b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
315 } 315 i+=2;
316 catch (UnsupportedEncodingException e) 316 }
317 { 317 else if (b==';')
318 s=new String(bytes,0,b); 318 {
319 } 319 length=i;
320 s.getChars(0,s.length(),chars,n); 320 break;
321 n+=s.length(); 321 }
322 b=0; 322 else if (bytes==null)
323 } 323 {
324 324 n++;
325 chars[n++]=c; 325 continue;
326 } 326 }
327 327
328 if (chars==null) 328 if (bytes==null)
329 return path; 329 {
330 330 bytes=new byte[length];
331 // if we have a remaining sequence of bytes 331 for (int j=0;j<n;j++)
332 if (b>0) 332 bytes[j]=buf[j + offset];
333 { 333 }
334 // convert series of bytes and add to chars 334
335 String s; 335 bytes[n++]=b;
336 try 336 }
337 { 337
338 s=new String(bytes,0,b,__CHARSET); 338 if (bytes==null)
339 } 339 return StringUtil.toString(buf,offset,length,__CHARSET);
340 catch (UnsupportedEncodingException e) 340 return StringUtil.toString(bytes,0,n,__CHARSET);
341 { 341 }
342 s=new String(bytes,0,b); 342
343 } 343
344 s.getChars(0,s.length(),chars,n); 344 /* ------------------------------------------------------------ */
345 n+=s.length(); 345 /** Add two URI path segments.
346 } 346 * Handles null and empty paths, path and query params (eg ?a=b or
347 347 * ;JSESSIONID=xxx) and avoids duplicate '/'
348 return new String(chars,0,n); 348 * @param p1 URI path segment (should be encoded)
349 } 349 * @param p2 URI path segment (should be encoded)
350 350 * @return Legally combined path segments.
351 /* ------------------------------------------------------------ */ 351 */
352 /* Decode a URI path and strip parameters. 352 public static String addPaths(String p1, String p2)
353 * @param path The path the encode 353 {
354 * @param buf StringBuilder to encode path into 354 if (p1==null || p1.length()==0)
355 */ 355 {
356 public static String decodePath(byte[] buf, int offset, int length) 356 if (p1!=null && p2==null)
357 { 357 return p1;
358 byte[] bytes=null; 358 return p2;
359 int n=0; 359 }
360 360 if (p2==null || p2.length()==0)
361 for (int i=0;i<length;i++) 361 return p1;
362 { 362
363 byte b = buf[i + offset]; 363 int split=p1.indexOf(';');
364 364 if (split<0)
365 if (b=='%' && (i+2)<length) 365 split=p1.indexOf('?');
366 { 366 if (split==0)
367 b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16)); 367 return p2+p1;
368 i+=2; 368 if (split<0)
369 } 369 split=p1.length();
370 else if (b==';') 370
371 { 371 StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
372 length=i; 372 buf.append(p1);
373 break; 373
374 } 374 if (buf.charAt(split-1)=='/')
375 else if (bytes==null) 375 {
376 { 376 if (p2.startsWith("/"))
377 n++; 377 {
378 continue; 378 buf.deleteCharAt(split-1);
379 } 379 buf.insert(split-1,p2);
380 380 }
381 if (bytes==null) 381 else
382 { 382 buf.insert(split,p2);
383 bytes=new byte[length]; 383 }
384 for (int j=0;j<n;j++) 384 else
385 bytes[j]=buf[j + offset]; 385 {
386 } 386 if (p2.startsWith("/"))
387 387 buf.insert(split,p2);
388 bytes[n++]=b; 388 else
389 } 389 {
390 390 buf.insert(split,'/');
391 if (bytes==null) 391 buf.insert(split+1,p2);
392 return StringUtil.toString(buf,offset,length,__CHARSET); 392 }
393 return StringUtil.toString(bytes,0,n,__CHARSET); 393 }
394 } 394
395 395 return buf.toString();
396 396 }
397 /* ------------------------------------------------------------ */ 397
398 /** Add two URI path segments. 398 /* ------------------------------------------------------------ */
399 * Handles null and empty paths, path and query params (eg ?a=b or 399 /** Return the parent Path.
400 * ;JSESSIONID=xxx) and avoids duplicate '/' 400 * Treat a URI like a directory path and return the parent directory.
401 * @param p1 URI path segment (should be encoded) 401 */
402 * @param p2 URI path segment (should be encoded) 402 public static String parentPath(String p)
403 * @return Legally combined path segments. 403 {
404 */ 404 if (p==null || "/".equals(p))
405 public static String addPaths(String p1, String p2) 405 return null;
406 { 406 int slash=p.lastIndexOf('/',p.length()-2);
407 if (p1==null || p1.length()==0) 407 if (slash>=0)
408 { 408 return p.substring(0,slash+1);
409 if (p1!=null && p2==null) 409 return null;
410 return p1; 410 }
411 return p2; 411
412 } 412 /* ------------------------------------------------------------ */
413 if (p2==null || p2.length()==0) 413 /** Convert a path to a cananonical form.
414 return p1; 414 * All instances of "." and ".." are factored out. Null is returned
415 415 * if the path tries to .. above its root.
416 int split=p1.indexOf(';'); 416 * @param path
417 if (split<0) 417 * @return path or null.
418 split=p1.indexOf('?'); 418 */
419 if (split==0) 419 public static String canonicalPath(String path)
420 return p2+p1; 420 {
421 if (split<0) 421 if (path==null || path.length()==0)
422 split=p1.length(); 422 return path;
423 423
424 StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2); 424 int end=path.length();
425 buf.append(p1); 425 int start = path.lastIndexOf('/', end);
426 426
427 if (buf.charAt(split-1)=='/') 427 search:
428 { 428 while (end>0)
429 if (p2.startsWith(URIUtil.SLASH)) 429 {
430 { 430 switch(end-start)
431 buf.deleteCharAt(split-1); 431 {
432 buf.insert(split-1,p2); 432 case 2: // possible single dot
433 } 433 if (path.charAt(start+1)!='.')
434 else 434 break;
435 buf.insert(split,p2); 435 break search;
436 } 436 case 3: // possible double dot
437 else 437 if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
438 { 438 break;
439 if (p2.startsWith(URIUtil.SLASH)) 439 break search;
440 buf.insert(split,p2); 440 }
441 else 441
442 { 442 end=start;
443 buf.insert(split,'/'); 443 start=path.lastIndexOf('/',end-1);
444 buf.insert(split+1,p2); 444 }
445 } 445
446 } 446 // If we have checked the entire string
447 447 if (start>=end)
448 return buf.toString(); 448 return path;
449 } 449
450 450 StringBuilder buf = new StringBuilder(path);
451 /* ------------------------------------------------------------ */ 451 int delStart=-1;
452 /** Return the parent Path. 452 int delEnd=-1;
453 * Treat a URI like a directory path and return the parent directory. 453 int skip=0;
454 */ 454
455 public static String parentPath(String p) 455 while (end>0)
456 { 456 {
457 if (p==null || URIUtil.SLASH.equals(p)) 457 switch(end-start)
458 return null; 458 {
459 int slash=p.lastIndexOf('/',p.length()-2); 459 case 2: // possible single dot
460 if (slash>=0) 460 if (buf.charAt(start+1)!='.')
461 return p.substring(0,slash+1); 461 {
462 return null; 462 if (skip>0 && --skip==0)
463 } 463 {
464 464 delStart=start>=0?start:0;
465 /* ------------------------------------------------------------ */ 465 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
466 /** Convert a path to a cananonical form. 466 delStart++;
467 * All instances of "." and ".." are factored out. Null is returned 467 }
468 * if the path tries to .. above its root. 468 break;
469 * @param path 469 }
470 * @return path or null. 470
471 */ 471 if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
472 public static String canonicalPath(String path) 472 break;
473 { 473
474 if (path==null || path.length()==0) 474 if(delEnd<0)
475 return path; 475 delEnd=end;
476 476 delStart=start;
477 int end=path.length(); 477 if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
478 int start = path.lastIndexOf('/', end); 478 {
479 479 delStart++;
480 search: 480 if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
481 while (end>0) 481 delEnd++;
482 { 482 break;
483 switch(end-start) 483 }
484 { 484 if (end==buf.length())
485 case 2: // possible single dot 485 delStart++;
486 if (path.charAt(start+1)!='.') 486
487 break; 487 end=start--;
488 break search; 488 while (start>=0 && buf.charAt(start)!='/')
489 case 3: // possible double dot 489 start--;
490 if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.') 490 continue;
491 break; 491
492 break search; 492 case 3: // possible double dot
493 } 493 if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
494 494 {
495 end=start; 495 if (skip>0 && --skip==0)
496 start=path.lastIndexOf('/',end-1); 496 { delStart=start>=0?start:0;
497 } 497 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
498 498 delStart++;
499 // If we have checked the entire string 499 }
500 if (start>=end) 500 break;
501 return path; 501 }
502 502
503 StringBuilder buf = new StringBuilder(path); 503 delStart=start;
504 int delStart=-1; 504 if (delEnd<0)
505 int delEnd=-1; 505 delEnd=end;
506 int skip=0; 506
507 507 skip++;
508 while (end>0) 508 end=start--;
509 { 509 while (start>=0 && buf.charAt(start)!='/')
510 switch(end-start) 510 start--;
511 { 511 continue;
512 case 2: // possible single dot 512
513 if (buf.charAt(start+1)!='.') 513 default:
514 { 514 if (skip>0 && --skip==0)
515 if (skip>0 && --skip==0) 515 {
516 { 516 delStart=start>=0?start:0;
517 delStart=start>=0?start:0; 517 if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
518 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') 518 delStart++;
519 delStart++; 519 }
520 } 520 }
521 break; 521
522 } 522 // Do the delete
523 523 if (skip<=0 && delStart>=0 && delEnd>=delStart)
524 if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/') 524 {
525 break; 525 buf.delete(delStart,delEnd);
526 526 delStart=delEnd=-1;
527 if(delEnd<0) 527 if (skip>0)
528 delEnd=end; 528 delEnd=end;
529 delStart=start; 529 }
530 if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/') 530
531 { 531 end=start--;
532 delStart++; 532 while (start>=0 && buf.charAt(start)!='/')
533 if (delEnd<buf.length() && buf.charAt(delEnd)=='/') 533 start--;
534 delEnd++; 534 }
535 break; 535
536 } 536 // Too many ..
537 if (end==buf.length()) 537 if (skip>0)
538 delStart++; 538 return null;
539 539
540 end=start--; 540 // Do the delete
541 while (start>=0 && buf.charAt(start)!='/') 541 if (delEnd>=0)
542 start--; 542 buf.delete(delStart,delEnd);
543 continue; 543
544 544 return buf.toString();
545 case 3: // possible double dot 545 }
546 if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.') 546
547 { 547 /* ------------------------------------------------------------ */
548 if (skip>0 && --skip==0) 548 /**
549 { delStart=start>=0?start:0; 549 * @param uri URI
550 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') 550 * @return True if the uri has a scheme
551 delStart++; 551 */
552 } 552 public static boolean hasScheme(String uri)
553 break; 553 {
554 } 554 for (int i=0;i<uri.length();i++)
555 555 {
556 delStart=start; 556 char c=uri.charAt(i);
557 if (delEnd<0) 557 if (c==':')
558 delEnd=end; 558 return true;
559 559 if (!(c>='a'&&c<='z' ||
560 skip++; 560 c>='A'&&c<='Z' ||
561 end=start--; 561 (i>0 &&(c>='0'&&c<='9' ||
562 while (start>=0 && buf.charAt(start)!='/') 562 c=='.' ||
563 start--; 563 c=='+' ||
564 continue; 564 c=='-'))
565 565 ))
566 default: 566 break;
567 if (skip>0 && --skip==0) 567 }
568 { 568 return false;
569 delStart=start>=0?start:0; 569 }
570 if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.') 570
571 delStart++;
572 }
573 }
574
575 // Do the delete
576 if (skip<=0 && delStart>=0 && delEnd>=delStart)
577 {
578 buf.delete(delStart,delEnd);
579 delStart=delEnd=-1;
580 if (skip>0)
581 delEnd=end;
582 }
583
584 end=start--;
585 while (start>=0 && buf.charAt(start)!='/')
586 start--;
587 }
588
589 // Too many ..
590 if (skip>0)
591 return null;
592
593 // Do the delete
594 if (delEnd>=0)
595 buf.delete(delStart,delEnd);
596
597 return buf.toString();
598 }
599
600 /* ------------------------------------------------------------ */
601 /** Convert a path to a compact form.
602 * All instances of "//" and "///" etc. are factored out to single "/"
603 * @param path
604 * @return path
605 */
606 public static String compactPath(String path)
607 {
608 if (path==null || path.length()==0)
609 return path;
610
611 int state=0;
612 int end=path.length();
613 int i=0;
614
615 loop:
616 while (i<end)
617 {
618 char c=path.charAt(i);
619 switch(c)
620 {
621 case '?':
622 return path;
623 case '/':
624 state++;
625 if (state==2)
626 break loop;
627 break;
628 default:
629 state=0;
630 }
631 i++;
632 }
633
634 if (state<2)
635 return path;
636
637 StringBuffer buf = new StringBuffer(path.length());
638 buf.append(path,0,i);
639
640 loop2:
641 while (i<end)
642 {
643 char c=path.charAt(i);
644 switch(c)
645 {
646 case '?':
647 buf.append(path,i,end);
648 break loop2;
649 case '/':
650 if (state++==0)
651 buf.append(c);
652 break;
653 default:
654 state=0;
655 buf.append(c);
656 }
657 i++;
658 }
659
660 return buf.toString();
661 }
662
663 /* ------------------------------------------------------------ */
664 /**
665 * @param uri URI
666 * @return True if the uri has a scheme
667 */
668 public static boolean hasScheme(String uri)
669 {
670 for (int i=0;i<uri.length();i++)
671 {
672 char c=uri.charAt(i);
673 if (c==':')
674 return true;
675 if (!(c>='a'&&c<='z' ||
676 c>='A'&&c<='Z' ||
677 (i>0 &&(c>='0'&&c<='9' ||
678 c=='.' ||
679 c=='+' ||
680 c=='-'))
681 ))
682 break;
683 }
684 return false;
685 }
686
687 } 571 }
688 572
689 573
690 574