1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
36
37
38
39
40
41
42
43
44
45
46
47
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
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
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 }