2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.util;
21 import java.nio.charset.Charset;
22 import java.nio.charset.StandardCharsets;
26 /* ------------------------------------------------------------ */
28 * This class assists with the decoding and encoding or HTTP URI's.
29 * It differs from the java.net.URL class as it does not provide
30 * communications ability, but it does assist with query string
32 * <P>UTF-8 encoding is used by default for % encoded characters. This
33 * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
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:";
46 // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
47 public static final Charset __CHARSET;
51 String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
52 __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
58 /* ------------------------------------------------------------ */
59 /** Encode a URI path.
60 * This is the same encoding offered by URLEncoder, except that
61 * the '/' character is not encoded.
62 * @param path The path the encode
63 * @return The encoded path
65 public static String encodePath(String path)
67 if (path==null || path.length()==0)
70 StringBuilder buf = encodePath(null,path);
71 return buf==null?path:buf.toString();
74 /* ------------------------------------------------------------ */
75 /** Encode a URI path.
76 * @param path The path the encode
77 * @param buf StringBuilder to encode path into (or null)
78 * @return The StringBuilder or null if no substitutions required.
80 public static StringBuilder encodePath(StringBuilder buf, String path)
86 for (int i=0;i<path.length();i++)
88 char c=path.charAt(i);
100 buf=new StringBuilder(path.length()*2);
105 bytes=path.getBytes(URIUtil.__CHARSET);
106 buf=new StringBuilder(path.length()*2);
120 for (int i=0;i<bytes.length;i++)
156 TypeUtil.toHex(c,buf);
167 for (int i=0;i<path.length();i++)
169 char c=path.charAt(i);
210 /* ------------------------------------------------------------ */
211 /** Encode a URI path.
212 * @param path The path the encode
213 * @param buf StringBuilder to encode path into (or null)
214 * @param encode String of characters to encode. % is always encoded.
215 * @return The StringBuilder or null if no substitutions required.
217 public static StringBuilder encodeString(StringBuilder buf,
224 for (int i=0;i<path.length();i++)
226 char c=path.charAt(i);
227 if (c=='%' || encode.indexOf(c)>=0)
229 buf=new StringBuilder(path.length()<<1);
239 for (int i=0;i<path.length();i++)
241 char c=path.charAt(i);
242 if (c=='%' || encode.indexOf(c)>=0)
245 StringUtil.append(buf,(byte)(0xff&c),16);
255 /* ------------------------------------------------------------ */
256 /* Decode a URI path and strip parameters
257 * @param path The path the encode
258 * @param buf StringBuilder to encode path into
260 public static String decodePath(String path)
264 // Array to hold all converted characters
267 // Array to hold a sequence of %encodings
271 int len=path.length();
273 for (int i=0;i<len;i++)
275 char c = path.charAt(i);
277 if (c=='%' && (i+2)<len)
283 path.getChars(0,i,chars,0);
285 bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
294 path.getChars(0,i,chars,0);
299 else if (bytes==null)
305 // Do we have some bytes to convert?
308 String s=new String(bytes,0,b,__CHARSET);
309 s.getChars(0,s.length(),chars,n);
320 // if we have a remaining sequence of bytes
323 String s=new String(bytes,0,b,__CHARSET);
324 s.getChars(0,s.length(),chars,n);
328 return new String(chars,0,n);
331 /* ------------------------------------------------------------ */
332 /* Decode a URI path and strip parameters.
333 * @param path The path the encode
334 * @param buf StringBuilder to encode path into
336 public static String decodePath(byte[] buf, int offset, int length)
341 for (int i=0;i<length;i++)
343 byte b = buf[i + offset];
345 if (b=='%' && (i+2)<length)
347 b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
355 else if (bytes==null)
363 bytes=new byte[length];
364 for (int j=0;j<n;j++)
365 bytes[j]=buf[j + offset];
372 return new String(buf,offset,length,__CHARSET);
373 return new String(bytes,0,n,__CHARSET);
377 /* ------------------------------------------------------------ */
378 /** Add two URI path segments.
379 * Handles null and empty paths, path and query params (eg ?a=b or
380 * ;JSESSIONID=xxx) and avoids duplicate '/'
381 * @param p1 URI path segment (should be encoded)
382 * @param p2 URI path segment (should be encoded)
383 * @return Legally combined path segments.
385 public static String addPaths(String p1, String p2)
387 if (p1==null || p1.length()==0)
389 if (p1!=null && p2==null)
393 if (p2==null || p2.length()==0)
396 int split=p1.indexOf(';');
398 split=p1.indexOf('?');
404 StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
407 if (buf.charAt(split-1)=='/')
409 if (p2.startsWith(URIUtil.SLASH))
411 buf.deleteCharAt(split-1);
412 buf.insert(split-1,p2);
415 buf.insert(split,p2);
419 if (p2.startsWith(URIUtil.SLASH))
420 buf.insert(split,p2);
423 buf.insert(split,'/');
424 buf.insert(split+1,p2);
428 return buf.toString();
431 /* ------------------------------------------------------------ */
432 /** Return the parent Path.
433 * Treat a URI like a directory path and return the parent directory.
435 public static String parentPath(String p)
437 if (p==null || URIUtil.SLASH.equals(p))
439 int slash=p.lastIndexOf('/',p.length()-2);
441 return p.substring(0,slash+1);
445 /* ------------------------------------------------------------ */
446 /** Convert a path to a cananonical form.
447 * All instances of "." and ".." are factored out. Null is returned
448 * if the path tries to .. above its root.
450 * @return path or null.
452 public static String canonicalPath(String path)
454 if (path==null || path.length()==0)
457 int end=path.length();
458 int start = path.lastIndexOf('/', end);
465 case 2: // possible single dot
466 if (path.charAt(start+1)!='.')
469 case 3: // possible double dot
470 if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
476 start=path.lastIndexOf('/',end-1);
479 // If we have checked the entire string
483 StringBuilder buf = new StringBuilder(path);
492 case 2: // possible single dot
493 if (buf.charAt(start+1)!='.')
495 if (skip>0 && --skip==0)
497 delStart=start>=0?start:0;
498 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
504 if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
510 if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
513 if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
517 if (end==buf.length())
521 while (start>=0 && buf.charAt(start)!='/')
525 case 3: // possible double dot
526 if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
528 if (skip>0 && --skip==0)
529 { delStart=start>=0?start:0;
530 if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
542 while (start>=0 && buf.charAt(start)!='/')
547 if (skip>0 && --skip==0)
549 delStart=start>=0?start:0;
550 if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
556 if (skip<=0 && delStart>=0 && delEnd>=delStart)
558 buf.delete(delStart,delEnd);
565 while (start>=0 && buf.charAt(start)!='/')
575 buf.delete(delStart,delEnd);
577 return buf.toString();
580 /* ------------------------------------------------------------ */
581 /** Convert a path to a compact form.
582 * All instances of "//" and "///" etc. are factored out to single "/"
586 public static String compactPath(String path)
588 if (path==null || path.length()==0)
592 int end=path.length();
598 char c=path.charAt(i);
617 StringBuffer buf = new StringBuffer(path.length());
618 buf.append(path,0,i);
623 char c=path.charAt(i);
627 buf.append(path,i,end);
640 return buf.toString();
643 /* ------------------------------------------------------------ */
646 * @return True if the uri has a scheme
648 public static boolean hasScheme(String uri)
650 for (int i=0;i<uri.length();i++)
652 char c=uri.charAt(i);
655 if (!(c>='a'&&c<='z' ||
657 (i>0 &&(c>='0'&&c<='9' ||
667 public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
669 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
670 url.append(scheme).append("://").append('[').append(server).append(']');
672 url.append(scheme).append("://").append(server);
674 if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
675 url.append(':').append(port);
678 public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
682 if (server.indexOf(':')>=0&&server.charAt(0)!='[')
683 url.append(scheme).append("://").append('[').append(server).append(']');
685 url.append(scheme).append("://").append(server);
687 if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
688 url.append(':').append(port);