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 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 }