View Javadoc

1   //========================================================================
2   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
3   //------------------------------------------------------------------------
4   //Licensed under the Apache License, Version 2.0 (the "License");
5   //you may not use this file except in compliance with the License.
6   //You may obtain a copy of the License at 
7   //http://www.apache.org/licenses/LICENSE-2.0
8   //Unless required by applicable law or agreed to in writing, software
9   //distributed under the License is distributed on an "AS IS" BASIS,
10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  //See the License for the specific language governing permissions and
12  //limitations under the License.
13  //========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.io.UnsupportedEncodingException;
18  
19  import org.mortbay.util.MultiMap;
20  import org.mortbay.util.StringUtil;
21  import org.mortbay.util.TypeUtil;
22  import org.mortbay.util.URIUtil;
23  import org.mortbay.util.UrlEncoded;
24  
25  
26  /* ------------------------------------------------------------ */
27  /** Http URI.
28   * Parse a HTTP URI from a string or byte array.  Given a URI
29   * <code>http://user@host:port/path/info;param?query#fragment</code>
30   * this class will split it into the following undecoded optional elements:<ul>
31   * <li>{@link #getScheme()} - http:</li>
32   * <li>{@link #getAuthority()} - //name@host:port</li>
33   * <li>{@link #getHost()} - host</li>
34   * <li>{@link #getPort()} - port</li>
35   * <li>{@link #getPath()} - /path/info</li>
36   * <li>{@link #getParam()} - param</li>
37   * <li>{@link #getQuery()} - query</li>
38   * <li>{@link #getFragment()} - fragment</li>
39   * </ul>
40   * 
41   */
42  public class HttpURI
43  {
44      private static byte[] __empty={}; 
45      private final static int 
46      START=0,
47      AUTH_OR_PATH=1,
48      SCHEME_OR_PATH=2,
49      AUTH=4,
50      IPV6=5,
51      PORT=6,
52      PATH=7,
53      PARAM=8,
54      QUERY=9,
55      ASTERISK=10;
56      
57      boolean _partial=false;
58      byte[] _raw=__empty;
59      String _rawString;
60      int _scheme;
61      int _authority;
62      int _host;
63      int _port;
64      int _path;
65      int _param;
66      int _query;
67      int _fragment;
68      int _end;
69      
70      public HttpURI()
71      {
72          
73      } 
74      
75      /* ------------------------------------------------------------ */
76      /**
77       * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
78       */
79      public HttpURI(boolean parsePartialAuth)
80      {
81          _partial=parsePartialAuth;
82      }
83      
84      public HttpURI(String raw)
85      {
86          _rawString=raw;
87          byte[] b = raw.getBytes();
88          parse(b,0,b.length);
89      }
90      
91      public HttpURI(byte[] raw,int offset, int length)
92      {
93          parse2(raw,offset,length);
94      }
95      
96      public void parse(String raw)
97      {
98          byte[] b = raw.getBytes();
99          parse2(b,0,b.length);
100         _rawString=raw;
101     }
102     
103     public void parse(byte[] raw,int offset, int length)
104     {
105         _rawString=null;
106         parse2(raw,offset,length);
107     }
108     
109     private void parse2(byte[] raw,int offset, int length)
110     {
111         _raw=raw;
112         int i=offset;
113         int e=offset+length;
114         int state=START;
115         int m=offset;
116         _end=offset+length;
117         _scheme=offset;
118         _authority=offset;
119         _host=offset;
120         _port=offset;
121         _path=offset;
122         _param=_end;
123         _query=_end;
124         _fragment=_end;
125         while (i<e)
126         {
127             char c=(char)(0xff&_raw[i]);
128             int s=i++;
129             
130             state: switch (state)
131             {
132                 case START:
133                 {
134                     m=s;
135                     switch(c)
136                     {
137                         case '/':
138                             state=AUTH_OR_PATH;
139                             break;
140                         case ';':
141                             _param=s;
142                             state=PARAM;
143                             break;
144                         case '?':
145                             _param=s;
146                             _query=s;
147                             state=QUERY;
148                             break;
149                         case '#':
150                             _param=s;
151                             _query=s;
152                             _fragment=s;
153                             break;
154                         case '*':
155                             _path=s;
156                             state=ASTERISK;
157                             break;
158                             
159                         default:
160                             if (Character.isLetterOrDigit(c))
161                                 state=SCHEME_OR_PATH;
162                             else
163                                 throw new IllegalArgumentException(StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
164                     }
165                     
166                     continue;
167                 }
168 
169                 case AUTH_OR_PATH:
170                 {
171                     if ((_partial||_scheme!=_authority) && c=='/')
172                     {
173                         _host=i;
174                         _port=_end;
175                         _path=_end;
176                         state=AUTH;
177                     }
178                     else if (c==';' || c=='?' || c=='#')
179                     {
180                         i--;
181                         state=PATH;
182                     }  
183                     else
184                     {
185                         _host=m;
186                         _port=m;
187                         state=PATH;
188                     }  
189                     continue;
190                 }
191                 
192                 case SCHEME_OR_PATH:
193                 {
194                     // short cut for http and https
195                     if (length>6 && c=='t')
196                     {
197                         if (_raw[offset+3]==':')
198                         {
199                             s=offset+3;
200                             i=offset+4;
201                             c=':';
202                         }
203                         else if (_raw[offset+4]==':')
204                         {
205                             s=offset+4;
206                             i=offset+5;
207                             c=':';
208                         }
209                         else if (_raw[offset+5]==':')
210                         {
211                             s=offset+5;
212                             i=offset+6;
213                             c=':';
214                         }
215                     }
216                     
217                     switch (c)
218                     {
219                         case ':':
220                         {
221                             m = i++;
222                             _authority = m;
223                             _path = m;
224                             c = (char)(0xff & _raw[i]);
225                             if (c == '/')
226                                 state = AUTH_OR_PATH;
227                             else
228                             {
229                                 _host = m;
230                                 _port = m;
231                                 state = PATH;
232                             }
233                             break;
234                         }
235                         
236                         case '/':
237                         {
238                             state = PATH;
239                             break;
240                         }
241                         
242                         case ';':
243                         {
244                             _param = s;
245                             state = PARAM;
246                             break;
247                         }
248                         
249                         case '?':
250                         {
251                             _param = s;
252                             _query = s;
253                             state = QUERY;
254                             break;
255                         }
256                         
257                         case '#':
258                         {
259                             _param = s;
260                             _query = s;
261                             _fragment = s;
262                             break;
263                         }
264                     }
265                     continue;
266                 }
267                 
268                 case AUTH:
269                 {
270                     switch (c)
271                     {
272 
273                         case '/':
274                         {
275                             m = s;
276                             _path = m;
277                             _port = _path;
278                             state = PATH;
279                             break;
280                         }
281                         case '@':
282                         {
283                             _host = i;
284                             break;
285                         }
286                         case ':':
287                         {
288                             _port = s;
289                             state = PORT;
290                             break;
291                         }
292                         case '[':
293                         {
294                             state = IPV6;
295                             break;
296                         }
297                     }
298                     continue;
299                 }
300 
301                 case IPV6:
302                 {
303                     switch (c)
304                     {
305                         case '/':
306                         {
307                             throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
308                         }
309                         case ']':
310                         {
311                             state = AUTH;
312                             break;
313                         }
314                     }
315 
316                     continue;
317                 }
318                 
319                 case PORT:
320                 {
321                     if (c=='/')
322                     {
323                         m=s;
324                         _path=m;
325                         if (_port<=_authority)
326                             _port=_path;
327                         state=PATH;
328                     }
329                     continue;
330                 }
331                 
332                 case PATH:
333                 {
334                     switch (c)
335                     {
336                         case ';':
337                         {
338                             _param = s;
339                             state = PARAM;
340                             break;
341                         }
342                         case '?':
343                         {
344                             _param = s;
345                             _query = s;
346                             state = QUERY;
347                             break;
348                         }
349                         case '#':
350                         {
351                             _param = s;
352                             _query = s;
353                             _fragment = s;
354                             break state;
355                         }
356                     }
357                     continue;
358                 }
359                 
360                 case PARAM:
361                 {
362                     switch (c)
363                     {
364                         case '?':
365                         {
366                             _query = s;
367                             state = QUERY;
368                             break;
369                         }
370                         case '#':
371                         {
372                             _query = s;
373                             _fragment = s;
374                             break state;
375                         }
376                     }
377                     continue;
378                 }
379                 
380                 case QUERY:
381                 {
382                     if (c=='#')
383                     {
384                         _fragment=s;
385                         break state;
386                     }
387                     continue;
388                 }
389                 
390                 case ASTERISK:
391                 {
392                     throw new IllegalArgumentException("only '*'");
393                 }
394             }
395         }
396     }
397     
398     
399     public String getScheme()
400     {
401         if (_scheme==_authority)
402             return null;
403         int l=_authority-_scheme;
404         if (l==5 && 
405             _raw[_scheme]=='h' && 
406             _raw[_scheme+1]=='t' && 
407             _raw[_scheme+2]=='t' && 
408             _raw[_scheme+3]=='p' )
409             return HttpSchemes.HTTP;
410         if (l==6 && 
411             _raw[_scheme]=='h' && 
412             _raw[_scheme+1]=='t' && 
413             _raw[_scheme+2]=='t' && 
414             _raw[_scheme+3]=='p' && 
415             _raw[_scheme+4]=='s' )
416             return HttpSchemes.HTTPS;
417         return StringUtil.toString(_raw,_scheme,_authority-_scheme-1,URIUtil.__CHARSET);
418     }
419     
420     public String getAuthority()
421     {
422         if (_authority==_path)
423             return null;
424         return StringUtil.toString(_raw,_authority,_path-_authority,URIUtil.__CHARSET);
425     }
426     
427     public String getHost()
428     {
429         if (_host==_port)
430             return null;
431         return StringUtil.toString(_raw,_host,_port-_host,URIUtil.__CHARSET);
432     }
433     
434     public int getPort()
435     {
436         if (_port==_path)
437             return -1;
438         return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
439     }
440     
441     public String getPath()
442     {
443         if (_path==_param)
444             return null;
445         return StringUtil.toString(_raw,_path,_param-_path,URIUtil.__CHARSET);
446     }
447     
448     public String getDecodedPath()
449     {
450         if (_path==_param)
451             return null;
452         return URIUtil.decodePath(_raw,_path,_param-_path);
453     }
454     
455     public String getPathAndParam()
456     {
457         if (_path==_query)
458             return null;
459         return StringUtil.toString(_raw,_path,_query-_path,URIUtil.__CHARSET);
460     }
461     
462     public String getCompletePath()
463     {
464         if (_path==_end)
465             return null;
466         return StringUtil.toString(_raw,_path,_end-_path,URIUtil.__CHARSET);
467     }
468     
469     public String getParam()
470     {
471         if (_param==_query)
472             return null;
473         return StringUtil.toString(_raw,_param+1,_query-_param-1,URIUtil.__CHARSET);
474     }
475     
476     public String getQuery()
477     {
478         if (_query==_fragment)
479             return null;
480         return StringUtil.toString(_raw,_query+1,_fragment-_query-1,URIUtil.__CHARSET);
481     }
482     
483     public String getQuery(String encoding)
484     {
485         if (_query==_fragment)
486             return null;
487         return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding==null?URIUtil.__CHARSET:encoding);
488     }
489     
490     public String getFragment()
491     {
492         if (_fragment==_end)
493             return null;
494         return StringUtil.toString(_raw,_fragment+1,_end-_fragment-1,URIUtil.__CHARSET);
495     }
496 
497     public void decodeQueryTo(MultiMap parameters, String encoding) 
498         throws UnsupportedEncodingException
499     {
500         if (_query==_fragment)
501             return;
502        
503         if (encoding==null)
504             encoding=URIUtil.__CHARSET;
505         
506         if (StringUtil.isUTF8(encoding))
507             UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
508         else
509             UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
510     }
511 
512     public void clear()
513     {
514         _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
515         _raw=__empty;
516         _rawString="";
517     }
518     
519     public String toString()
520     {
521         if (_rawString==null)
522             _rawString= StringUtil.toString(_raw,_scheme,_end-_scheme,URIUtil.__CHARSET);
523         return _rawString;
524     }
525     
526 }