]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/util/URIUtil.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / util / URIUtil.java
1 //
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.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
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 /** URI Holder.
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
31  * formatting.
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.
34  * @see UrlEncoded
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     // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
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     /** 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
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     /** 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.
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     /** 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.
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     /* Decode a URI path and strip parameters
257      * @param path The path the encode
258      * @param buf StringBuilder to encode path into
259      */
260     public static String decodePath(String path)
261     {
262         if (path==null)
263             return null;
264         // Array to hold all converted characters
265         char[] chars=null;
266         int n=0;
267         // Array to hold a sequence of %encodings
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             // Do we have some bytes to convert?
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         // if we have a remaining sequence of bytes
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     /* Decode a URI path and strip parameters.
333      * @param path The path the encode
334      * @param buf StringBuilder to encode path into
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     /** 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.
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     /** Return the parent Path.
433      * Treat a URI like a directory path and return the parent directory.
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     /** 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.
449      * @param path 
450      * @return path or null.
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: // possible single dot
466                   if (path.charAt(start+1)!='.')
467                       break;
468                   break search;
469               case 3: // possible double dot
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         // If we have checked the entire string
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: // possible single dot
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: // possible double dot
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             // Do the delete
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         // Too many ..
570         if (skip>0)
571             return null;
572         
573         // Do the delete
574         if (delEnd>=0)
575             buf.delete(delStart,delEnd);
576
577         return buf.toString();
578     }
579
580     /* ------------------------------------------------------------ */
581     /** Convert a path to a compact form.
582      * All instances of "//" and "///" etc. are factored out to single "/" 
583      * @param path 
584      * @return path
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      * @param uri URI
646      * @return True if the uri has a scheme
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