View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.http;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  import java.nio.charset.Charset;
25  import java.nio.charset.StandardCharsets;
26  
27  import org.eclipse.jetty.util.MultiMap;
28  import org.eclipse.jetty.util.TypeUtil;
29  import org.eclipse.jetty.util.URIUtil;
30  import org.eclipse.jetty.util.UrlEncoded;
31  
32  
33  /* ------------------------------------------------------------ */
34  /** Http URI.
35   * Parse a HTTP URI from a string or byte array.  Given a URI
36   * <code>http://user@host:port/path/info;param?query#fragment</code>
37   * this class will split it into the following undecoded optional elements:<ul>
38   * <li>{@link #getScheme()} - http:</li>
39   * <li>{@link #getAuthority()} - //name@host:port</li>
40   * <li>{@link #getHost()} - host</li>
41   * <li>{@link #getPort()} - port</li>
42   * <li>{@link #getPath()} - /path/info</li>
43   * <li>{@link #getParam()} - param</li>
44   * <li>{@link #getQuery()} - query</li>
45   * <li>{@link #getFragment()} - fragment</li>
46   * </ul>
47   * 
48   * <p>Any parameters will be returned from {@link #getPath()}, but are excluded from the
49   * return value of {@link #getDecodedPath()}.   If there are multiple parameters, the 
50   * {@link #getParam()} method returns only the last one.
51   */
52  public class HttpURI
53  {
54      private enum State {
55      START,
56      HOST_OR_PATH,
57      SCHEME_OR_PATH,
58      HOST,
59      IPV6,
60      PORT,
61      PATH,
62      PARAM,
63      QUERY,
64      FRAGMENT,
65      ASTERISK};
66  
67      private String _scheme;
68      private String _user;
69      private String _host;
70      private int _port;
71      private String _path;
72      private String _param;
73      private String _query;
74      private String _fragment;
75      
76      String _uri;
77      String _decodedPath;
78  
79      /* ------------------------------------------------------------ */
80      /**
81       * Construct a normalized URI.
82       * Port is not set if it is the default port.
83       * @param scheme the URI scheme
84       * @param host the URI hose
85       * @param port the URI port
86       * @param path the URI path
87       * @param param the URI param
88       * @param query the URI query
89       * @param fragment the URI fragment
90       * @return the normalized URI
91       */
92      public static HttpURI createHttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
93      {
94          if (port==80 && HttpScheme.HTTP.is(scheme))
95              port=0;
96          if (port==443 && HttpScheme.HTTPS.is(scheme))
97              port=0;
98          return new HttpURI(scheme,host,port,path,param,query,fragment);
99      }
100     
101     /* ------------------------------------------------------------ */
102     public HttpURI()
103     {
104     }
105 
106     /* ------------------------------------------------------------ */
107     public HttpURI(String scheme, String host, int port, String path, String param, String query, String fragment)
108     {
109         _scheme = scheme;
110         _host = host;
111         _port = port;
112         _path = path;
113         _param = param;
114         _query = query;
115         _fragment = fragment;
116     }
117 
118     /* ------------------------------------------------------------ */
119     public HttpURI(HttpURI uri)
120     {
121         this(uri._scheme,uri._host,uri._port,uri._path,uri._param,uri._query,uri._fragment);
122     }
123     
124     /* ------------------------------------------------------------ */
125     public HttpURI(String uri)
126     {
127         _port=-1;
128         parse(State.START,uri,0,uri.length());
129     }
130 
131     /* ------------------------------------------------------------ */
132     public HttpURI(URI uri)
133     {
134         _uri=null;
135         
136         _scheme=uri.getScheme();
137         _host=uri.getHost();
138         if (_host==null && uri.getRawSchemeSpecificPart().startsWith("//"))
139             _host="";
140         _port=uri.getPort();
141         _user = uri.getUserInfo();
142         _path=uri.getRawPath();
143         
144         _decodedPath = uri.getPath();
145         if (_decodedPath != null)
146         {
147             int p = _decodedPath.lastIndexOf(';');
148             if (p >= 0)
149                 _param = _decodedPath.substring(p + 1);
150         }
151         _query=uri.getRawQuery();
152         _fragment=uri.getFragment();
153         
154         _decodedPath=null;
155     }
156 
157     /* ------------------------------------------------------------ */
158     public HttpURI(String scheme, String host, int port, String pathQuery)
159     {
160         _uri=null;
161         
162         _scheme=scheme;
163         _host=host;
164         _port=port;
165 
166         parse(State.PATH,pathQuery,0,pathQuery.length());
167         
168     }
169 
170     /* ------------------------------------------------------------ */
171     public void parse(String uri)
172     {
173         clear();
174         _uri=uri;
175         parse(State.START,uri,0,uri.length());
176     }
177 
178     /* ------------------------------------------------------------ */
179     public void parseConnect(String uri)
180     {
181         clear();
182         _uri=uri;
183         _path=uri;
184     }
185 
186     /* ------------------------------------------------------------ */
187     public void parse(String uri, int offset, int length)
188     {
189         clear();
190         int end=offset+length;
191         _uri=uri.substring(offset,end);
192         parse(State.START,uri,offset,end);
193     }
194 
195     /* ------------------------------------------------------------ */
196     private void parse(State state, final String uri, final int offset, final int end)
197     {
198         boolean encoded=false;
199         int mark=offset;
200         int path_mark=0;
201         
202         for (int i=offset; i<end; i++)
203         {
204             char c=uri.charAt(i);
205 
206             switch (state)
207             {
208                 case START:
209                 {
210                     switch(c)
211                     {
212                         case '/':
213                             mark = i;
214                             state = State.HOST_OR_PATH;
215                             break;
216                         case ';':
217                             mark=i+1;
218                             state=State.PARAM;
219                             break;
220                         case '?':
221                             // assume empty path (if seen at start)
222                             _path = "";
223                             mark=i+1;
224                             state=State.QUERY;
225                             break;
226                         case '#':
227                             mark=i+1;
228                             state=State.FRAGMENT;
229                             break;
230                         case '*':
231                             _path="*";
232                             state=State.ASTERISK;
233                             break;
234 
235                         default:
236                             mark=i;
237                             if (_scheme==null)
238                                 state=State.SCHEME_OR_PATH;
239                             else
240                             {
241                                 path_mark=i;
242                                 state=State.PATH;
243                             }
244                     }
245 
246                     continue;
247                 }
248 
249                 case SCHEME_OR_PATH:
250                 {
251                     switch (c)
252                     {
253                         case ':':
254                             // must have been a scheme
255                             _scheme=uri.substring(mark,i);
256                             // Start again with scheme set
257                             state=State.START;
258                             break;
259 
260                         case '/':
261                             // must have been in a path and still are
262                             state=State.PATH;
263                             break;
264 
265                         case ';':
266                             // must have been in a path 
267                             mark=i+1;
268                             state=State.PARAM;
269                             break;
270 
271                         case '?':
272                             // must have been in a path 
273                             _path=uri.substring(mark,i);
274                             mark=i+1;
275                             state=State.QUERY;
276                             break;
277 
278                         case '%':
279                             // must have be in an encoded path 
280                             encoded=true;
281                             state=State.PATH;
282                             break;
283                         
284                         case '#':
285                             // must have been in a path 
286                             _path=uri.substring(mark,i);
287                             state=State.FRAGMENT;
288                             break;
289                     }
290                     continue;
291                 }
292                 
293                 case HOST_OR_PATH:
294                 {
295                     switch(c)
296                     {
297                         case '/':
298                             _host="";
299                             mark=i+1;
300                             state=State.HOST;
301                             break;
302                             
303                         case '@':
304                             _user=uri.substring(mark,i);
305                             mark=i+1;
306                             break;
307                             
308                         case ';':
309                         case '?':
310                         case '#':
311                             // was a path, look again
312                             i--;
313                             path_mark=mark;
314                             state=State.PATH;
315                             break;
316                         default:
317                             // it is a path
318                             path_mark=mark;
319                             state=State.PATH;
320                     }
321                     continue;
322                 }
323 
324                 case HOST:
325                 {
326                     switch (c)
327                     {
328                         case '/':
329                             _host = uri.substring(mark,i);
330                             path_mark=mark=i;
331                             state=State.PATH;
332                             break;
333                         case ':':
334                             if (i > mark)
335                                 _host=uri.substring(mark,i);
336                             mark=i+1;
337                             state=State.PORT;
338                             break;
339                         case '@':
340                             _user=uri.substring(mark,i);
341                             mark=i+1;
342                             break;
343                             
344                         case '[':
345                             state=State.IPV6;
346                             break;
347                     }
348                     continue;
349                 }
350 
351                 case IPV6:
352                 {
353                     switch (c)
354                     {
355                         case '/':
356                             throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
357                         case ']':
358                             c = uri.charAt(++i);
359                             _host=uri.substring(mark,i);
360                             if (c == ':')
361                             {
362                                 mark=i+1;
363                                 state=State.PORT;
364                             }
365                             else
366                             {
367                                 path_mark=mark=i;
368                                 state=State.PATH;
369                             }
370                             break;
371                     }
372 
373                     continue;
374                 }
375 
376                 case PORT:
377                 {
378                     if (c=='/')
379                     {
380                         _port=TypeUtil.parseInt(uri,mark,i-mark,10);
381                         path_mark=mark=i;
382                         state=State.PATH;
383                     }
384                     continue;
385                 }
386 
387                 case PATH:
388                 {
389                     switch (c)
390                     {
391                         case ';':
392                             mark=i+1;
393                             state=State.PARAM;
394                             break;
395                         case '?':
396                             _path=uri.substring(path_mark,i);
397                             mark=i+1;
398                             state=State.QUERY;
399                             break;
400                         case '#':
401                             _path=uri.substring(path_mark,i);
402                             mark=i+1;
403                             state=State.FRAGMENT;
404                             break;
405                         case '%':
406                             encoded=true;
407                             break;
408                     }
409                     continue;
410                 }
411 
412                 case PARAM:
413                 {
414                     switch (c)
415                     {
416                         case '?':
417                             _path=uri.substring(path_mark,i);
418                             _param=uri.substring(mark,i);
419                             mark=i+1;
420                             state=State.QUERY;
421                             break;
422                         case '#':
423                             _path=uri.substring(path_mark,i);
424                             _param=uri.substring(mark,i);
425                             mark=i+1;
426                             state=State.FRAGMENT;
427                             break;
428                         case '/':
429                             encoded=true;
430                             // ignore internal params
431                             state=State.PATH;
432                             break;
433                         case ';':
434                             // multiple parameters
435                             mark=i+1;
436                             break;
437                     }
438                     continue;
439                 }
440 
441                 case QUERY:
442                 {
443                     if (c=='#')
444                     {
445                         _query=uri.substring(mark,i);
446                         mark=i+1;
447                         state=State.FRAGMENT;
448                     }
449                     continue;
450                 }
451 
452                 case ASTERISK:
453                 {
454                     throw new IllegalArgumentException("only '*'");
455                 }
456                 
457                 case FRAGMENT:
458                 {
459                     _fragment=uri.substring(mark,end);
460                     i=end;
461                 }
462             }
463         }
464 
465         
466         switch(state)
467         {
468             case START:
469                 break;
470             case SCHEME_OR_PATH:
471                 _path=uri.substring(mark,end);
472                 break;
473 
474             case HOST_OR_PATH:
475                 _path=uri.substring(mark,end);
476                 break;
477                 
478             case HOST:
479                 if(end>mark)
480                     _host=uri.substring(mark,end);
481                 break;
482                 
483             case IPV6:
484                 throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
485 
486             case PORT:
487                 _port=TypeUtil.parseInt(uri,mark,end-mark,10);
488                 break;
489                 
490             case ASTERISK:
491                 break;
492                 
493             case FRAGMENT:
494                 _fragment=uri.substring(mark,end);
495                 break;
496                 
497             case PARAM:
498                 _path=uri.substring(path_mark,end);
499                 _param=uri.substring(mark,end);
500                 break;
501                 
502             case PATH:
503                 _path=uri.substring(path_mark,end);
504                 break;
505                 
506             case QUERY:
507                 _query=uri.substring(mark,end);
508                 break;
509         }
510         
511         if (!encoded)
512         {
513             if (_param==null)
514                 _decodedPath=_path;
515             else
516                 _decodedPath=_path.substring(0,_path.length()-_param.length()-1);
517         }
518     }
519 
520     /* ------------------------------------------------------------ */
521     public String getScheme()
522     {
523         return _scheme;
524     }
525 
526     /* ------------------------------------------------------------ */
527     public String getHost()
528     {
529         // Return null for empty host to retain compatibility with java.net.URI
530         if (_host!=null && _host.length()==0)
531             return null;
532         return _host;
533     }
534 
535     /* ------------------------------------------------------------ */
536     public int getPort()
537     {
538         return _port;
539     }
540 
541     /* ------------------------------------------------------------ */
542     /**
543      * The parsed Path.
544      * 
545      * @return the path as parsed on valid URI.  null for invalid URI.
546      */
547     public String getPath()
548     {
549         return _path;
550     }
551 
552     /* ------------------------------------------------------------ */
553     public String getDecodedPath()
554     {
555         if (_decodedPath==null && _path!=null)
556             _decodedPath=URIUtil.decodePath(_path);
557         return _decodedPath;
558     }
559 
560     /* ------------------------------------------------------------ */
561     public String getParam()
562     {
563         return _param;
564     }
565 
566     /* ------------------------------------------------------------ */
567     public String getQuery()
568     {
569         return _query;
570     }
571 
572     /* ------------------------------------------------------------ */
573     public boolean hasQuery()
574     {
575         return _query!=null && _query.length()>0;
576     }
577 
578     /* ------------------------------------------------------------ */
579     public String getFragment()
580     {
581         return _fragment;
582     }
583 
584     /* ------------------------------------------------------------ */
585     public void decodeQueryTo(MultiMap<String> parameters)
586     {
587         if (_query==_fragment)
588             return;
589         UrlEncoded.decodeUtf8To(_query,parameters);
590     }
591 
592     /* ------------------------------------------------------------ */
593     public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
594     {
595         decodeQueryTo(parameters,Charset.forName(encoding));
596     }
597 
598     /* ------------------------------------------------------------ */
599     public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
600     {
601         if (_query==_fragment)
602             return;
603 
604         if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
605             UrlEncoded.decodeUtf8To(_query,parameters);
606         else
607             UrlEncoded.decodeTo(_query,parameters,encoding);
608     }
609 
610     /* ------------------------------------------------------------ */
611     public void clear()
612     {
613         _uri=null;
614 
615         _scheme=null;
616         _host=null;
617         _port=-1;
618         _path=null;
619         _param=null;
620         _query=null;
621         _fragment=null;
622 
623         _decodedPath=null;
624     }
625 
626     /* ------------------------------------------------------------ */
627     public boolean isAbsolute()
628     {
629         return _scheme!=null && _scheme.length()>0;
630     }
631     
632     /* ------------------------------------------------------------ */
633     @Override
634     public String toString()
635     {
636         if (_uri==null)
637         {
638             StringBuilder out = new StringBuilder();
639             
640             if (_scheme!=null)
641                 out.append(_scheme).append(':');
642             
643             if (_host != null)
644             {
645                 out.append("//");
646                 if (_user != null)
647                     out.append(_user).append('@');
648                 out.append(_host);
649             }
650             
651             if (_port>0)
652                 out.append(':').append(_port);
653             
654             if (_path!=null)
655                 out.append(_path);
656             
657             if (_query!=null)
658                 out.append('?').append(_query);
659             
660             if (_fragment!=null)
661                 out.append('#').append(_fragment);
662             
663             if (out.length()>0)
664                 _uri=out.toString();
665             else
666                 _uri="";
667         }
668         return _uri;
669     }
670 
671     /* ------------------------------------------------------------ */
672     public boolean equals(Object o)
673     {
674         if (o==this)
675             return true;
676         if (!(o instanceof HttpURI))
677             return false;
678         return toString().equals(o.toString());
679     }
680 
681     /* ------------------------------------------------------------ */
682     public void setScheme(String scheme)
683     {
684         _scheme=scheme;
685         _uri=null;
686     }
687     
688     /* ------------------------------------------------------------ */
689     /**
690      * @param host the host
691      * @param port the port
692      */
693     public void setAuthority(String host, int port)
694     {
695         _host=host;
696         _port=port;
697         _uri=null;
698     }
699 
700     /* ------------------------------------------------------------ */
701     /**
702      * @param path the path
703      */
704     public void setPath(String path)
705     {
706         _uri=null;
707         _path=path;
708         _decodedPath=null;
709     }
710     
711     /* ------------------------------------------------------------ */
712     public void setPathQuery(String path)
713     {
714         _uri=null;
715         _path=null;
716         _decodedPath=null;
717         _param=null;
718         _fragment=null;
719         if (path!=null)
720             parse(State.PATH,path,0,path.length());
721     }
722     
723     /* ------------------------------------------------------------ */
724     public void setQuery(String query)
725     {
726         _query=query;
727         _uri=null;
728     }
729     
730     /* ------------------------------------------------------------ */
731     public URI toURI() throws URISyntaxException
732     {
733         return new URI(_scheme,null,_host,_port,_path,_query==null?null:UrlEncoded.decodeString(_query),_fragment);
734     }
735 
736     /* ------------------------------------------------------------ */
737     public String getPathQuery()
738     {
739         if (_query==null)
740             return _path;
741         return _path+"?"+_query;
742     }
743     
744     /* ------------------------------------------------------------ */
745     public String getAuthority()
746     {
747         if (_port>0)
748             return _host+":"+_port;
749         return _host;
750     }
751 
752 
753 }