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