View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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                         case ';':
305                         case '?':
306                         case '#':
307                             // was a path, look again
308                             i--;
309                             path_mark=mark;
310                             state=State.PATH;
311                             break;
312                         default:
313                             // it is a path
314                             path_mark=mark;
315                             state=State.PATH;
316                     }
317                     continue;
318                 }
319 
320                 case HOST:
321                 {
322                     switch (c)
323                     {
324                         case '/':
325                             _host = uri.substring(mark,i);
326                             path_mark=mark=i;
327                             state=State.PATH;
328                             break;
329                         case ':':
330                             if (i > mark)
331                                 _host=uri.substring(mark,i);
332                             mark=i+1;
333                             state=State.PORT;
334                             break;
335                         case '@':
336                             _user=uri.substring(mark,i);
337                             mark=i+1;
338                             break;
339                             
340                         case '[':
341                             state=State.IPV6;
342                             break;
343                     }
344                     continue;
345                 }
346 
347                 case IPV6:
348                 {
349                     switch (c)
350                     {
351                         case '/':
352                             throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
353                         case ']':
354                             c = uri.charAt(++i);
355                             _host=uri.substring(mark,i);
356                             if (c == ':')
357                             {
358                                 mark=i+1;
359                                 state=State.PORT;
360                             }
361                             else
362                             {
363                                 path_mark=mark=i;
364                                 state=State.PATH;
365                             }
366                             break;
367                     }
368 
369                     continue;
370                 }
371 
372                 case PORT:
373                 {
374                     if (c=='/')
375                     {
376                         _port=TypeUtil.parseInt(uri,mark,i-mark,10);
377                         path_mark=mark=i;
378                         state=State.PATH;
379                     }
380                     continue;
381                 }
382 
383                 case PATH:
384                 {
385                     switch (c)
386                     {
387                         case ';':
388                             mark=i+1;
389                             state=State.PARAM;
390                             break;
391                         case '?':
392                             _path=uri.substring(path_mark,i);
393                             mark=i+1;
394                             state=State.QUERY;
395                             break;
396                         case '#':
397                             _path=uri.substring(path_mark,i);
398                             mark=i+1;
399                             state=State.FRAGMENT;
400                             break;
401                         case '%':
402                             encoded=true;
403                             break;
404                     }
405                     continue;
406                 }
407 
408                 case PARAM:
409                 {
410                     switch (c)
411                     {
412                         case '?':
413                             _path=uri.substring(path_mark,i);
414                             _param=uri.substring(mark,i);
415                             mark=i+1;
416                             state=State.QUERY;
417                             break;
418                         case '#':
419                             _path=uri.substring(path_mark,i);
420                             _param=uri.substring(mark,i);
421                             mark=i+1;
422                             state=State.FRAGMENT;
423                             break;
424                         case '/':
425                             encoded=true;
426                             // ignore internal params
427                             state=State.PATH;
428                             break;
429                         case ';':
430                             // multiple parameters
431                             mark=i+1;
432                             break;
433                     }
434                     continue;
435                 }
436 
437                 case QUERY:
438                 {
439                     if (c=='#')
440                     {
441                         _query=uri.substring(mark,i);
442                         mark=i+1;
443                         state=State.FRAGMENT;
444                     }
445                     continue;
446                 }
447 
448                 case ASTERISK:
449                 {
450                     throw new IllegalArgumentException("only '*'");
451                 }
452                 
453                 case FRAGMENT:
454                 {
455                     _fragment=uri.substring(mark,end);
456                     i=end;
457                 }
458             }
459         }
460 
461         
462         switch(state)
463         {
464             case START:
465                 break;
466             case SCHEME_OR_PATH:
467                 _path=uri.substring(mark,end);
468                 break;
469 
470             case HOST_OR_PATH:
471                 _path=uri.substring(mark,end);
472                 break;
473                 
474             case HOST:
475                 if(end>mark)
476                     _host=uri.substring(mark,end);
477                 break;
478                 
479             case IPV6:
480                 throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri);
481 
482             case PORT:
483                 _port=TypeUtil.parseInt(uri,mark,end-mark,10);
484                 break;
485                 
486             case ASTERISK:
487                 break;
488                 
489             case FRAGMENT:
490                 _fragment=uri.substring(mark,end);
491                 break;
492                 
493             case PARAM:
494                 _path=uri.substring(path_mark,end);
495                 _param=uri.substring(mark,end);
496                 break;
497                 
498             case PATH:
499                 _path=uri.substring(path_mark,end);
500                 break;
501                 
502             case QUERY:
503                 _query=uri.substring(mark,end);
504                 break;
505         }
506         
507         if (!encoded)
508         {
509             if (_param==null)
510                 _decodedPath=_path;
511             else
512                 _decodedPath=_path.substring(0,_path.length()-_param.length()-1);
513         }
514     }
515 
516     /* ------------------------------------------------------------ */
517     public String getScheme()
518     {
519         return _scheme;
520     }
521 
522     /* ------------------------------------------------------------ */
523     public String getHost()
524     {
525         // Return null for empty host to retain compatibility with java.net.URI
526         if (_host!=null && _host.length()==0)
527             return null;
528         return _host;
529     }
530 
531     /* ------------------------------------------------------------ */
532     public int getPort()
533     {
534         return _port;
535     }
536 
537     /* ------------------------------------------------------------ */
538     /**
539      * The parsed Path.
540      * 
541      * @return the path as parsed on valid URI.  null for invalid URI.
542      */
543     public String getPath()
544     {
545         return _path;
546     }
547 
548     /* ------------------------------------------------------------ */
549     public String getDecodedPath()
550     {
551         if (_decodedPath==null && _path!=null)
552             _decodedPath=URIUtil.decodePath(_path);
553         return _decodedPath;
554     }
555 
556     /* ------------------------------------------------------------ */
557     public String getParam()
558     {
559         return _param;
560     }
561 
562     /* ------------------------------------------------------------ */
563     public String getQuery()
564     {
565         return _query;
566     }
567 
568     /* ------------------------------------------------------------ */
569     public boolean hasQuery()
570     {
571         return _query!=null && _query.length()>0;
572     }
573 
574     /* ------------------------------------------------------------ */
575     public String getFragment()
576     {
577         return _fragment;
578     }
579 
580     /* ------------------------------------------------------------ */
581     public void decodeQueryTo(MultiMap<String> parameters)
582     {
583         if (_query==_fragment)
584             return;
585         UrlEncoded.decodeUtf8To(_query,parameters);
586     }
587 
588     /* ------------------------------------------------------------ */
589     public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
590     {
591         decodeQueryTo(parameters,Charset.forName(encoding));
592     }
593 
594     /* ------------------------------------------------------------ */
595     public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
596     {
597         if (_query==_fragment)
598             return;
599 
600         if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
601             UrlEncoded.decodeUtf8To(_query,parameters);
602         else
603             UrlEncoded.decodeTo(_query,parameters,encoding);
604     }
605 
606     /* ------------------------------------------------------------ */
607     public void clear()
608     {
609         _uri=null;
610 
611         _scheme=null;
612         _host=null;
613         _port=-1;
614         _path=null;
615         _param=null;
616         _query=null;
617         _fragment=null;
618 
619         _decodedPath=null;
620     }
621 
622     /* ------------------------------------------------------------ */
623     public boolean isAbsolute()
624     {
625         return _scheme!=null && _scheme.length()>0;
626     }
627     
628     /* ------------------------------------------------------------ */
629     @Override
630     public String toString()
631     {
632         if (_uri==null)
633         {
634             StringBuilder out = new StringBuilder();
635             
636             if (_scheme!=null)
637                 out.append(_scheme).append(':');
638             
639             if (_host != null)
640             {
641                 out.append("//");
642                 if (_user != null)
643                     out.append(_user).append('@');
644                 out.append(_host);
645             }
646             
647             if (_port>0)
648                 out.append(':').append(_port);
649             
650             if (_path!=null)
651                 out.append(_path);
652             
653             if (_query!=null)
654                 out.append('?').append(_query);
655             
656             if (_fragment!=null)
657                 out.append('#').append(_fragment);
658             
659             if (out.length()>0)
660                 _uri=out.toString();
661             else
662                 _uri="";
663         }
664         return _uri;
665     }
666 
667     /* ------------------------------------------------------------ */
668     public boolean equals(Object o)
669     {
670         if (o==this)
671             return true;
672         if (!(o instanceof HttpURI))
673             return false;
674         return toString().equals(o.toString());
675     }
676 
677     /* ------------------------------------------------------------ */
678     public void setScheme(String scheme)
679     {
680         _scheme=scheme;
681         _uri=null;
682     }
683     
684     /* ------------------------------------------------------------ */
685     /**
686      * @param host the host
687      * @param port the port
688      */
689     public void setAuthority(String host, int port)
690     {
691         _host=host;
692         _port=port;
693         _uri=null;
694     }
695 
696     /* ------------------------------------------------------------ */
697     /**
698      * @param path the path
699      */
700     public void setPath(String path)
701     {
702         _uri=null;
703         _path=path;
704         _decodedPath=null;
705     }
706     
707     /* ------------------------------------------------------------ */
708     public void setPathQuery(String path)
709     {
710         _uri=null;
711         _path=null;
712         _decodedPath=null;
713         _param=null;
714         _fragment=null;
715         if (path!=null)
716             parse(State.PATH,path,0,path.length());
717     }
718     
719     /* ------------------------------------------------------------ */
720     public void setQuery(String query)
721     {
722         _query=query;
723         _uri=null;
724     }
725     
726     /* ------------------------------------------------------------ */
727     public URI toURI() throws URISyntaxException
728     {
729         return new URI(_scheme,null,_host,_port,_path,_query==null?null:UrlEncoded.decodeString(_query),_fragment);
730     }
731 
732     /* ------------------------------------------------------------ */
733     public String getPathQuery()
734     {
735         if (_query==null)
736             return _path;
737         return _path+"?"+_query;
738     }
739     
740     /* ------------------------------------------------------------ */
741     public String getAuthority()
742     {
743         if (_port>0)
744             return _host+":"+_port;
745         return _host;
746     }
747 
748 
749 }