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