]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/http/HttpURI.java
Merge "Update notes about password security"
[gigi.git] / lib / jetty / org / eclipse / jetty / http / HttpURI.java
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.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         return new String(_raw,_host,_port-_host,_charset);
543     }
544
545     public int getPort()
546     {
547         return _portValue;
548     }
549
550     public String getPath()
551     {
552         if (_path==_param)
553             return null;
554         return new String(_raw,_path,_param-_path,_charset);
555     }
556
557     public String getDecodedPath()
558     {
559         if (_path==_param)
560             return null;
561
562         Utf8StringBuilder utf8b=null;
563
564         for (int i=_path;i<_param;i++)
565         {
566             byte b = _raw[i];
567
568             if (b=='%')
569             {
570                 if (utf8b==null)
571                 {
572                     utf8b=new Utf8StringBuilder();
573                     utf8b.append(_raw,_path,i-_path);
574                 }
575                 
576                 if ((i+2)>=_param)
577                     throw new IllegalArgumentException("Bad % encoding: "+this);
578                 if (_raw[i+1]=='u')
579                 {
580                     if ((i+5)>=_param)
581                         throw new IllegalArgumentException("Bad %u encoding: "+this);
582                     try
583                     {
584                         String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
585                         utf8b.getStringBuilder().append(unicode);
586                         i+=5;
587                     }
588                     catch(Exception e)
589                     {
590                         throw new RuntimeException(e);
591                     }
592                 }
593                 else
594                 {
595                     b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
596                     utf8b.append(b);
597                     i+=2;
598                 }
599                 continue;
600             }
601             else if (utf8b!=null)
602             {
603                 utf8b.append(b);
604             }
605         }
606
607         if (utf8b==null)
608             return StringUtil.toUTF8String(_raw, _path, _param-_path);
609         return utf8b.toString();
610     }
611
612     public String getDecodedPath(String encoding)
613     {
614         return getDecodedPath(Charset.forName(encoding));
615     }
616
617     public String getDecodedPath(Charset encoding)
618     {
619         if (_path==_param)
620             return null;
621
622         int length = _param-_path;
623         byte[] bytes=null;
624         int n=0;
625
626         for (int i=_path;i<_param;i++)
627         {
628             byte b = _raw[i];
629
630             if (b=='%')
631             {
632                 if (bytes==null)
633                 {
634                     bytes=new byte[length];
635                     System.arraycopy(_raw,_path,bytes,0,n);
636                 }
637                 
638                 if ((i+2)>=_param)
639                     throw new IllegalArgumentException("Bad % encoding: "+this);
640                 if (_raw[i+1]=='u')
641                 {
642                     if ((i+5)>=_param)
643                         throw new IllegalArgumentException("Bad %u encoding: "+this);
644
645                     try
646                     {
647                         String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
648                         byte[] encoded = unicode.getBytes(encoding);
649                         System.arraycopy(encoded,0,bytes,n,encoded.length);
650                         n+=encoded.length;
651                         i+=5;
652                     }
653                     catch(Exception e)
654                     {
655                         throw new RuntimeException(e);
656                     }
657                 }
658                 else
659                 {
660                     b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
661                     bytes[n++]=b;
662                     i+=2;
663                 }
664                 continue;
665             }
666             else if (bytes==null)
667             {
668                 n++;
669                 continue;
670             }
671
672             bytes[n++]=b;
673         }
674
675
676         if (bytes==null)
677             return new String(_raw,_path,_param-_path,encoding);
678
679         return new String(bytes,0,n,encoding);
680     }
681
682     public String getPathAndParam()
683     {
684         if (_path==_query)
685             return null;
686         return new String(_raw,_path,_query-_path,_charset);
687     }
688
689     public String getCompletePath()
690     {
691         if (_path==_end)
692             return null;
693         return new String(_raw,_path,_end-_path,_charset);
694     }
695
696     public String getParam()
697     {
698         if (_param==_query)
699             return null;
700         return new String(_raw,_param+1,_query-_param-1,_charset);
701     }
702
703     public String getQuery()
704     {
705         if (_query==_fragment)
706             return null;
707         return new String(_raw,_query+1,_fragment-_query-1,_charset);
708     }
709
710     public String getQuery(String encoding)
711     {
712         if (_query==_fragment)
713             return null;
714         return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
715     }
716
717     public boolean hasQuery()
718     {
719         return (_fragment>_query);
720     }
721
722     public String getFragment()
723     {
724         if (_fragment==_end)
725             return null;
726         return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
727     }
728
729     public void decodeQueryTo(MultiMap<String> parameters)
730     {
731         if (_query==_fragment)
732             return;
733         if (_charset.equals(StandardCharsets.UTF_8))
734             UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
735         else
736             UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
737     }
738
739     public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
740     {
741         if (_query==_fragment)
742             return;
743
744         if (encoding==null || StringUtil.isUTF8(encoding))
745             UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
746         else
747             UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
748     }
749
750     public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
751     {
752         if (_query==_fragment)
753             return;
754
755         if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
756             UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
757         else
758             UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
759     }
760
761     public void clear()
762     {
763         _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
764         _raw=__empty;
765         _rawString="";
766         _encoded=false;
767     }
768
769     @Override
770     public String toString()
771     {
772         if (_rawString==null)
773             _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
774         return _rawString;
775     }
776
777     public void writeTo(Utf8StringBuilder buf)
778     {
779         buf.append(_raw,_scheme,_end-_scheme);
780     }
781
782 }