1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.util;
15
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.InputStreamReader;
19 import java.io.UnsupportedEncodingException;
20 import java.util.Iterator;
21 import java.util.Map;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 public class UrlEncoded extends MultiMap
44 {
45 public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8);
46
47
48 public UrlEncoded(UrlEncoded url)
49 {
50 super(url);
51 }
52
53
54 public UrlEncoded()
55 {
56 super(6);
57 }
58
59
60 public UrlEncoded(String s)
61 {
62 super(6);
63 decode(s,ENCODING);
64 }
65
66
67 public UrlEncoded(String s, String charset)
68 {
69 super(6);
70 decode(s,charset);
71 }
72
73
74 public void decode(String query)
75 {
76 decodeTo(query,this,ENCODING);
77 }
78
79
80 public void decode(String query,String charset)
81 {
82 decodeTo(query,this,charset);
83 }
84
85
86
87
88 public String encode()
89 {
90 return encode(ENCODING,false);
91 }
92
93
94
95
96 public String encode(String charset)
97 {
98 return encode(charset,false);
99 }
100
101
102
103
104
105
106 public synchronized String encode(String charset, boolean equalsForNullValue)
107 {
108 return encode(this,charset,equalsForNullValue);
109 }
110
111
112
113
114
115
116 public static String encode(MultiMap map, String charset, boolean equalsForNullValue)
117 {
118 if (charset==null)
119 charset=ENCODING;
120
121 StringBuilder result = new StringBuilder(128);
122
123 Iterator iter = map.entrySet().iterator();
124 while(iter.hasNext())
125 {
126 Map.Entry entry = (Map.Entry)iter.next();
127
128 String key = entry.getKey().toString();
129 Object list = entry.getValue();
130 int s=LazyList.size(list);
131
132 if (s==0)
133 {
134 result.append(encodeString(key,charset));
135 if(equalsForNullValue)
136 result.append('=');
137 }
138 else
139 {
140 for (int i=0;i<s;i++)
141 {
142 if (i>0)
143 result.append('&');
144 Object val=LazyList.get(list,i);
145 result.append(encodeString(key,charset));
146
147 if (val!=null)
148 {
149 String str=val.toString();
150 if (str.length()>0)
151 {
152 result.append('=');
153 result.append(encodeString(str,charset));
154 }
155 else if (equalsForNullValue)
156 result.append('=');
157 }
158 else if (equalsForNullValue)
159 result.append('=');
160 }
161 }
162 if (iter.hasNext())
163 result.append('&');
164 }
165 return result.toString();
166 }
167
168
169
170
171
172
173
174 public static void decodeTo(String content, MultiMap map, String charset)
175 {
176 if (charset==null)
177 charset=ENCODING;
178
179 synchronized(map)
180 {
181 String key = null;
182 String value = null;
183 int mark=-1;
184 boolean encoded=false;
185 for (int i=0;i<content.length();i++)
186 {
187 char c = content.charAt(i);
188 switch (c)
189 {
190 case '&':
191 int l=i-mark-1;
192 value = l==0?"":
193 (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i));
194 mark=i;
195 encoded=false;
196 if (key != null)
197 {
198 map.add(key,value);
199 }
200 else if (value!=null&&value.length()>0)
201 {
202 map.add(value,"");
203 }
204 key = null;
205 value=null;
206 break;
207 case '=':
208 if (key!=null)
209 break;
210 key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i);
211 mark=i;
212 encoded=false;
213 break;
214 case '+':
215 encoded=true;
216 break;
217 case '%':
218 encoded=true;
219 break;
220 }
221 }
222
223 if (key != null)
224 {
225 int l=content.length()-mark-1;
226 value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1));
227 map.add(key,value);
228 }
229 else if (mark<content.length())
230 {
231 key = encoded
232 ?decodeString(content,mark+1,content.length()-mark-1,charset)
233 :content.substring(mark+1);
234 if (key != null && key.length() > 0)
235 {
236 map.add(key,"");
237 }
238 }
239 }
240 }
241
242
243
244
245
246
247
248
249 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map)
250 {
251 decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder());
252 }
253
254
255
256
257
258
259
260
261
262 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer)
263 {
264 synchronized(map)
265 {
266 String key = null;
267 String value = null;
268
269
270 int end=offset+length;
271 for (int i=offset;i<end;i++)
272 {
273 byte b=raw[i];
274 switch ((char)(0xff&b))
275 {
276 case '&':
277 value = buffer.length()==0?"":buffer.toString();
278 buffer.reset();
279 if (key != null)
280 {
281 map.add(key,value);
282 }
283 else if (value!=null&&value.length()>0)
284 {
285 map.add(value,"");
286 }
287 key = null;
288 value=null;
289 break;
290
291 case '=':
292 if (key!=null)
293 {
294 buffer.append(b);
295 break;
296 }
297 key = buffer.toString();
298 buffer.reset();
299 break;
300
301 case '+':
302 buffer.append((byte)' ');
303 break;
304
305 case '%':
306 if (i+2<end)
307 buffer.append((byte)((TypeUtil.convertHexDigit(raw[++i])<<4) + TypeUtil.convertHexDigit(raw[++i])));
308 break;
309 default:
310 buffer.append(b);
311 break;
312 }
313 }
314
315 if (key != null)
316 {
317 value = buffer.length()==0?"":buffer.toString();
318 buffer.reset();
319 map.add(key,value);
320 }
321 else if (buffer.length()>0)
322 {
323 map.add(buffer.toString(),"");
324 }
325 }
326 }
327
328
329
330
331
332
333
334 public static void decode88591To(InputStream in, MultiMap map, int maxLength)
335 throws IOException
336 {
337 synchronized(map)
338 {
339 StringBuffer buffer = new StringBuffer();
340 String key = null;
341 String value = null;
342
343 int b;
344
345
346 int totalLength=0;
347 while ((b=in.read())>=0)
348 {
349 switch ((char) b)
350 {
351 case '&':
352 value = buffer.length()==0?"":buffer.toString();
353 buffer.setLength(0);
354 if (key != null)
355 {
356 map.add(key,value);
357 }
358 else if (value!=null&&value.length()>0)
359 {
360 map.add(value,"");
361 }
362 key = null;
363 value=null;
364 break;
365
366 case '=':
367 if (key!=null)
368 {
369 buffer.append((char)b);
370 break;
371 }
372 key = buffer.toString();
373 buffer.setLength(0);
374 break;
375
376 case '+':
377 buffer.append(' ');
378 break;
379
380 case '%':
381 int dh=in.read();
382 int dl=in.read();
383 if (dh<0||dl<0)
384 break;
385 buffer.append((char)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
386 break;
387 default:
388 buffer.append((char)b);
389 break;
390 }
391 if (maxLength>=0 && (++totalLength > maxLength))
392 throw new IllegalStateException("Form too large");
393 }
394
395 if (key != null)
396 {
397 value = buffer.length()==0?"":buffer.toString();
398 buffer.setLength(0);
399 map.add(key,value);
400 }
401 else if (buffer.length()>0)
402 {
403 map.add(buffer.toString(), "");
404 }
405 }
406 }
407
408
409
410
411
412
413
414 public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength)
415 throws IOException
416 {
417 synchronized(map)
418 {
419 Utf8StringBuilder buffer = new Utf8StringBuilder();
420 String key = null;
421 String value = null;
422
423 int b;
424
425
426 int totalLength=0;
427 while ((b=in.read())>=0)
428 {
429 switch ((char) b)
430 {
431 case '&':
432 value = buffer.length()==0?"":buffer.toString();
433 buffer.reset();
434 if (key != null)
435 {
436 map.add(key,value);
437 }
438 else if (value!=null&&value.length()>0)
439 {
440 map.add(value,"");
441 }
442 key = null;
443 value=null;
444 break;
445
446 case '=':
447 if (key!=null)
448 {
449 buffer.append((byte)b);
450 break;
451 }
452 key = buffer.toString();
453 buffer.reset();
454 break;
455
456 case '+':
457 buffer.append((byte)' ');
458 break;
459
460 case '%':
461 int dh=in.read();
462 int dl=in.read();
463 if (dh<0||dl<0)
464 break;
465 buffer.append((byte)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
466 break;
467 default:
468 buffer.append((byte)b);
469 break;
470 }
471 if (maxLength>=0 && (++totalLength > maxLength))
472 throw new IllegalStateException("Form too large");
473 }
474
475 if (key != null)
476 {
477 value = buffer.length()==0?"":buffer.toString();
478 buffer.reset();
479 map.add(key,value);
480 }
481 else if (buffer.length()>0)
482 {
483 map.add(buffer.toString(), "");
484 }
485 }
486 }
487
488
489 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException
490 {
491 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
492 StringBuffer buf = new StringBuffer();
493
494 int c;
495 int length=0;
496 if (maxLength<0)
497 maxLength=Integer.MAX_VALUE;
498 while ((c=input.read())>0 && length++<maxLength)
499 buf.append((char)c);
500 decodeTo(buf.toString(),map,ENCODING);
501 }
502
503
504
505
506
507 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength)
508 throws IOException
509 {
510
511 if (charset==null)
512 {
513 charset=ENCODING;
514 }
515
516
517 if (StringUtil.__UTF8.equalsIgnoreCase(charset))
518 {
519 decodeUtf8To(in,map,maxLength);
520 return;
521 }
522
523 if (StringUtil.__ISO_8859_1.equals(charset))
524 {
525 decode88591To(in,map,maxLength);
526 return;
527 }
528
529 if (StringUtil.__UTF16.equalsIgnoreCase(charset))
530 {
531 decodeUtf16To(in,map,maxLength);
532 return;
533 }
534
535
536 synchronized(map)
537 {
538 String key = null;
539 String value = null;
540
541 int c;
542 int digit=0;
543 int digits=0;
544
545 int totalLength = 0;
546 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
547
548 int size=0;
549
550 while ((c=in.read())>0)
551 {
552 switch ((char) c)
553 {
554 case '&':
555 size=output.size();
556 value = size==0?"":output.toString(charset);
557 output.setCount(0);
558 if (key != null)
559 {
560 map.add(key,value);
561 }
562 else if (value!=null&&value.length()>0)
563 {
564 map.add(value,"");
565 }
566 key = null;
567 value=null;
568 break;
569 case '=':
570 if (key!=null)
571 {
572 output.write(c);
573 break;
574 }
575 size=output.size();
576 key = size==0?"":output.toString(charset);
577 output.setCount(0);
578 break;
579 case '+':
580 output.write(' ');
581 break;
582 case '%':
583 digits=2;
584 break;
585 default:
586 if (digits==2)
587 {
588 digit=TypeUtil.convertHexDigit((byte)c);
589 digits=1;
590 }
591 else if (digits==1)
592 {
593 output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c));
594 digits=0;
595 }
596 else
597 output.write(c);
598 break;
599 }
600
601 totalLength++;
602 if (maxLength>=0 && totalLength > maxLength)
603 throw new IllegalStateException("Form too large");
604 }
605
606 size=output.size();
607 if (key != null)
608 {
609 value = size==0?"":output.toString(charset);
610 output.setCount(0);
611 map.add(key,value);
612 }
613 else if (size>0)
614 map.add(output.toString(charset),"");
615 }
616 }
617
618
619
620
621
622
623 public static String decodeString(String encoded,int offset,int length,String charset)
624 {
625 if (charset==null || StringUtil.isUTF8(charset))
626 {
627 Utf8StringBuffer buffer=null;
628
629 for (int i=0;i<length;i++)
630 {
631 char c = encoded.charAt(offset+i);
632 if (c<0||c>0xff)
633 {
634 if (buffer==null)
635 {
636 buffer=new Utf8StringBuffer(length);
637 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
638 }
639 else
640 buffer.getStringBuffer().append(c);
641 }
642 else if (c=='+')
643 {
644 if (buffer==null)
645 {
646 buffer=new Utf8StringBuffer(length);
647 buffer.getStringBuffer().append(encoded,offset,offset+i);
648 }
649
650 buffer.getStringBuffer().append(' ');
651 }
652 else if (c=='%' && (i+2)<length)
653 {
654 if (buffer==null)
655 {
656 buffer=new Utf8StringBuffer(length);
657 buffer.getStringBuffer().append(encoded,offset,offset+i);
658 }
659
660 try
661 {
662 byte b=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
663 buffer.append(b);
664 i+=2;
665 }
666 catch(NumberFormatException nfe)
667 {
668 buffer.getStringBuffer().append('%');
669 }
670 }
671 else if (buffer!=null)
672 buffer.getStringBuffer().append(c);
673 }
674
675 if (buffer==null)
676 {
677 if (offset==0 && encoded.length()==length)
678 return encoded;
679 return encoded.substring(offset,offset+length);
680 }
681
682 return buffer.toString();
683 }
684 else
685 {
686 StringBuffer buffer=null;
687
688 try
689 {
690 for (int i=0;i<length;i++)
691 {
692 char c = encoded.charAt(offset+i);
693 if (c<0||c>0xff)
694 {
695 if (buffer==null)
696 {
697 buffer=new StringBuffer(length);
698 buffer.append(encoded,offset,offset+i+1);
699 }
700 else
701 buffer.append(c);
702 }
703 else if (c=='+')
704 {
705 if (buffer==null)
706 {
707 buffer=new StringBuffer(length);
708 buffer.append(encoded,offset,offset+i);
709 }
710
711 buffer.append(' ');
712 }
713 else if (c=='%' && (i+2)<length)
714 {
715 if (buffer==null)
716 {
717 buffer=new StringBuffer(length);
718 buffer.append(encoded,offset,offset+i);
719 }
720
721 byte[] ba=new byte[length];
722 int n=0;
723 while(c>=0 && c<=0xff)
724 {
725 if (c=='%')
726 {
727 if(i+2<length)
728 {
729 try
730 {
731 ba[n++]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
732 i+=3;
733 }
734 catch(NumberFormatException nfe)
735 {
736 ba[n-1] = (byte)'%';
737 for(char next; ((next=encoded.charAt(++i+offset))!='%');)
738 ba[n++] = (byte)(next=='+' ? ' ' : next);
739 }
740 }
741 else
742 {
743 ba[n++] = (byte)'%';
744 i++;
745 }
746 }
747 else if (c=='+')
748 {
749 ba[n++]=(byte)' ';
750 i++;
751 }
752 else
753 {
754 ba[n++]=(byte)c;
755 i++;
756 }
757
758 if (i>=length)
759 break;
760 c = encoded.charAt(offset+i);
761 }
762
763 i--;
764 buffer.append(new String(ba,0,n,charset));
765
766 }
767 else if (buffer!=null)
768 buffer.append(c);
769 }
770
771 if (buffer==null)
772 {
773 if (offset==0 && encoded.length()==length)
774 return encoded;
775 return encoded.substring(offset,offset+length);
776 }
777
778 return buffer.toString();
779 }
780 catch (UnsupportedEncodingException e)
781 {
782 throw new RuntimeException(e);
783 }
784 }
785
786 }
787
788
789
790
791
792
793 public static String encodeString(String string)
794 {
795 return encodeString(string,ENCODING);
796 }
797
798
799
800
801
802
803 public static String encodeString(String string,String charset)
804 {
805 if (charset==null)
806 charset=ENCODING;
807 byte[] bytes=null;
808 try
809 {
810 bytes=string.getBytes(charset);
811 }
812 catch(UnsupportedEncodingException e)
813 {
814
815 bytes=string.getBytes();
816 }
817
818 int len=bytes.length;
819 byte[] encoded= new byte[bytes.length*3];
820 int n=0;
821 boolean noEncode=true;
822
823 for (int i=0;i<len;i++)
824 {
825 byte b = bytes[i];
826
827 if (b==' ')
828 {
829 noEncode=false;
830 encoded[n++]=(byte)'+';
831 }
832 else if (b>='a' && b<='z' ||
833 b>='A' && b<='Z' ||
834 b>='0' && b<='9')
835 {
836 encoded[n++]=b;
837 }
838 else
839 {
840 noEncode=false;
841 encoded[n++]=(byte)'%';
842 byte nibble= (byte) ((b&0xf0)>>4);
843 if (nibble>=10)
844 encoded[n++]=(byte)('A'+nibble-10);
845 else
846 encoded[n++]=(byte)('0'+nibble);
847 nibble= (byte) (b&0xf);
848 if (nibble>=10)
849 encoded[n++]=(byte)('A'+nibble-10);
850 else
851 encoded[n++]=(byte)('0'+nibble);
852 }
853 }
854
855 if (noEncode)
856 return string;
857
858 try
859 {
860 return new String(encoded,0,n,charset);
861 }
862 catch(UnsupportedEncodingException e)
863 {
864
865 return new String(encoded,0,n);
866 }
867 }
868
869
870
871
872
873 @Override
874 public Object clone()
875 {
876 return new UrlEncoded(this);
877 }
878 }