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 map.add(key,"");
235 }
236 }
237 }
238
239
240
241
242
243
244
245
246 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map)
247 {
248 decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder());
249 }
250
251
252
253
254
255
256
257
258
259 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer)
260 {
261 synchronized(map)
262 {
263 String key = null;
264 String value = null;
265
266
267 int end=offset+length;
268 for (int i=offset;i<end;i++)
269 {
270 byte b=raw[i];
271 switch ((char)(0xff&b))
272 {
273 case '&':
274 value = buffer.length()==0?"":buffer.toString();
275 buffer.reset();
276 if (key != null)
277 {
278 map.add(key,value);
279 }
280 else if (value!=null&&value.length()>0)
281 {
282 map.add(value,"");
283 }
284 key = null;
285 value=null;
286 break;
287
288 case '=':
289 if (key!=null)
290 {
291 buffer.append(b);
292 break;
293 }
294 key = buffer.toString();
295 buffer.reset();
296 break;
297
298 case '+':
299 buffer.append((byte)' ');
300 break;
301
302 case '%':
303 if (i+2<end)
304 buffer.append((byte)((TypeUtil.convertHexDigit(raw[++i])<<4) + TypeUtil.convertHexDigit(raw[++i])));
305 break;
306 default:
307 buffer.append(b);
308 break;
309 }
310 }
311
312 if (key != null)
313 {
314 value = buffer.length()==0?"":buffer.toString();
315 buffer.reset();
316 map.add(key,value);
317 }
318 else if (buffer.length()>0)
319 {
320 map.add(buffer.toString(),"");
321 }
322 }
323 }
324
325
326
327
328
329
330
331 public static void decode88591To(InputStream in, MultiMap map, int maxLength)
332 throws IOException
333 {
334 synchronized(map)
335 {
336 StringBuffer buffer = new StringBuffer();
337 String key = null;
338 String value = null;
339
340 int b;
341
342
343 int totalLength=0;
344 while ((b=in.read())>=0)
345 {
346 switch ((char) b)
347 {
348 case '&':
349 value = buffer.length()==0?"":buffer.toString();
350 buffer.setLength(0);
351 if (key != null)
352 {
353 map.add(key,value);
354 }
355 else if (value!=null&&value.length()>0)
356 {
357 map.add(value,"");
358 }
359 key = null;
360 value=null;
361 break;
362
363 case '=':
364 if (key!=null)
365 {
366 buffer.append((char)b);
367 break;
368 }
369 key = buffer.toString();
370 buffer.setLength(0);
371 break;
372
373 case '+':
374 buffer.append(' ');
375 break;
376
377 case '%':
378 int dh=in.read();
379 int dl=in.read();
380 if (dh<0||dl<0)
381 break;
382 buffer.append((char)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
383 break;
384 default:
385 buffer.append((char)b);
386 break;
387 }
388 if (maxLength>=0 && (++totalLength > maxLength))
389 throw new IllegalStateException("Form too large");
390 }
391
392 if (key != null)
393 {
394 value = buffer.length()==0?"":buffer.toString();
395 buffer.setLength(0);
396 map.add(key,value);
397 }
398 else if (buffer.length()>0)
399 {
400 map.add(buffer.toString(), "");
401 }
402 }
403 }
404
405
406
407
408
409
410
411 public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength)
412 throws IOException
413 {
414 synchronized(map)
415 {
416 Utf8StringBuilder buffer = new Utf8StringBuilder();
417 String key = null;
418 String value = null;
419
420 int b;
421
422
423 int totalLength=0;
424 while ((b=in.read())>=0)
425 {
426 switch ((char) b)
427 {
428 case '&':
429 value = buffer.length()==0?"":buffer.toString();
430 buffer.reset();
431 if (key != null)
432 {
433 map.add(key,value);
434 }
435 else if (value!=null&&value.length()>0)
436 {
437 map.add(value,"");
438 }
439 key = null;
440 value=null;
441 break;
442
443 case '=':
444 if (key!=null)
445 {
446 buffer.append((byte)b);
447 break;
448 }
449 key = buffer.toString();
450 buffer.reset();
451 break;
452
453 case '+':
454 buffer.append((byte)' ');
455 break;
456
457 case '%':
458 int dh=in.read();
459 int dl=in.read();
460 if (dh<0||dl<0)
461 break;
462 buffer.append((byte)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
463 break;
464 default:
465 buffer.append((byte)b);
466 break;
467 }
468 if (maxLength>=0 && (++totalLength > maxLength))
469 throw new IllegalStateException("Form too large");
470 }
471
472 if (key != null)
473 {
474 value = buffer.length()==0?"":buffer.toString();
475 buffer.reset();
476 map.add(key,value);
477 }
478 else if (buffer.length()>0)
479 {
480 map.add(buffer.toString(), "");
481 }
482 }
483 }
484
485
486 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException
487 {
488 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
489 StringBuffer buf = new StringBuffer();
490
491 int c;
492 int length=0;
493 if (maxLength<0)
494 maxLength=Integer.MAX_VALUE;
495 while ((c=input.read())>0 && length++<maxLength)
496 buf.append((char)c);
497 decodeTo(buf.toString(),map,ENCODING);
498 }
499
500
501
502
503
504 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength)
505 throws IOException
506 {
507 if (charset==null || StringUtil.__UTF8.equalsIgnoreCase(charset))
508 {
509 decodeUtf8To(in,map,maxLength);
510 return;
511 }
512
513 if (StringUtil.__ISO_8859_1.equals(charset))
514 {
515 decode88591To(in,map,maxLength);
516 return;
517 }
518
519 if (StringUtil.__UTF16.equalsIgnoreCase(charset))
520 {
521 decodeUtf16To(in,map,maxLength);
522 return;
523 }
524
525
526 synchronized(map)
527 {
528 String key = null;
529 String value = null;
530
531 int c;
532 int digit=0;
533 int digits=0;
534
535 int totalLength = 0;
536 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
537
538 int size=0;
539
540 while ((c=in.read())>0)
541 {
542 switch ((char) c)
543 {
544 case '&':
545 size=output.size();
546 value = size==0?"":output.toString(charset);
547 output.setCount(0);
548 if (key != null)
549 {
550 map.add(key,value);
551 }
552 else if (value!=null&&value.length()>0)
553 {
554 map.add(value,"");
555 }
556 key = null;
557 value=null;
558 break;
559 case '=':
560 if (key!=null)
561 {
562 output.write(c);
563 break;
564 }
565 size=output.size();
566 key = size==0?"":output.toString(charset);
567 output.setCount(0);
568 break;
569 case '+':
570 output.write(' ');
571 break;
572 case '%':
573 digits=2;
574 break;
575 default:
576 if (digits==2)
577 {
578 digit=TypeUtil.convertHexDigit((byte)c);
579 digits=1;
580 }
581 else if (digits==1)
582 {
583 output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c));
584 digits=0;
585 }
586 else
587 output.write(c);
588 break;
589 }
590
591 totalLength++;
592 if (maxLength>=0 && totalLength > maxLength)
593 throw new IllegalStateException("Form too large");
594 }
595
596 size=output.size();
597 if (key != null)
598 {
599 value = size==0?"":output.toString(charset);
600 output.setCount(0);
601 map.add(key,value);
602 }
603 else if (size>0)
604 map.add(output.toString(charset),"");
605 }
606 }
607
608
609
610
611
612
613 public static String decodeString(String encoded,int offset,int length,String charset)
614 {
615 if (charset==null || StringUtil.isUTF8(charset))
616 {
617 Utf8StringBuffer buffer=null;
618
619 for (int i=0;i<length;i++)
620 {
621 char c = encoded.charAt(offset+i);
622 if (c<0||c>0xff)
623 {
624 if (buffer==null)
625 {
626 buffer=new Utf8StringBuffer(length);
627 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
628 }
629 else
630 buffer.getStringBuffer().append(c);
631 }
632 else if (c=='+')
633 {
634 if (buffer==null)
635 {
636 buffer=new Utf8StringBuffer(length);
637 buffer.getStringBuffer().append(encoded,offset,offset+i);
638 }
639
640 buffer.getStringBuffer().append(' ');
641 }
642 else if (c=='%' && (i+2)<length)
643 {
644 if (buffer==null)
645 {
646 buffer=new Utf8StringBuffer(length);
647 buffer.getStringBuffer().append(encoded,offset,offset+i);
648 }
649
650 while(c=='%' && (i+2)<length)
651 {
652 try
653 {
654 byte b=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
655 buffer.append(b);
656 i+=3;
657 }
658 catch(NumberFormatException nfe)
659 {
660 buffer.getStringBuffer().append('%');
661 for(char next; ((next=encoded.charAt(++i+offset))!='%');)
662 buffer.getStringBuffer().append((next=='+' ? ' ' : next));
663 }
664
665 if (i<length)
666 c = encoded.charAt(offset+i);
667 }
668 i--;
669 }
670 else if (buffer!=null)
671 buffer.getStringBuffer().append(c);
672 }
673
674 if (buffer==null)
675 {
676 if (offset==0 && encoded.length()==length)
677 return encoded;
678 return encoded.substring(offset,offset+length);
679 }
680
681 return buffer.toString();
682 }
683 else
684 {
685 StringBuffer buffer=null;
686
687 try
688 {
689 for (int i=0;i<length;i++)
690 {
691 char c = encoded.charAt(offset+i);
692 if (c<0||c>0xff)
693 {
694 if (buffer==null)
695 {
696 buffer=new StringBuffer(length);
697 buffer.append(encoded,offset,offset+i+1);
698 }
699 else
700 buffer.append(c);
701 }
702 else if (c=='+')
703 {
704 if (buffer==null)
705 {
706 buffer=new StringBuffer(length);
707 buffer.append(encoded,offset,offset+i);
708 }
709
710 buffer.append(' ');
711 }
712 else if (c=='%' && (i+2)<length)
713 {
714 if (buffer==null)
715 {
716 buffer=new StringBuffer(length);
717 buffer.append(encoded,offset,offset+i);
718 }
719
720 byte[] ba=new byte[length];
721 int n=0;
722 while(c>=0 && c<=0xff)
723 {
724 if (c=='%')
725 {
726 if(i+2<length)
727 {
728 try
729 {
730 ba[n++]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
731 i+=3;
732 }
733 catch(NumberFormatException nfe)
734 {
735 ba[n-1] = (byte)'%';
736 for(char next; ((next=encoded.charAt(++i+offset))!='%');)
737 ba[n++] = (byte)(next=='+' ? ' ' : next);
738 }
739 }
740 else
741 {
742 ba[n++] = (byte)'%';
743 i++;
744 }
745 }
746 else if (c=='+')
747 {
748 ba[n++]=(byte)' ';
749 i++;
750 }
751 else
752 {
753 ba[n++]=(byte)c;
754 i++;
755 }
756
757 if (i>=length)
758 break;
759 c = encoded.charAt(offset+i);
760 }
761
762 i--;
763 buffer.append(new String(ba,0,n,charset));
764
765 }
766 else if (buffer!=null)
767 buffer.append(c);
768 }
769
770 if (buffer==null)
771 {
772 if (offset==0 && encoded.length()==length)
773 return encoded;
774 return encoded.substring(offset,offset+length);
775 }
776
777 return buffer.toString();
778 }
779 catch (UnsupportedEncodingException e)
780 {
781 throw new RuntimeException(e);
782 }
783 }
784
785 }
786
787
788
789
790
791
792 public static String encodeString(String string)
793 {
794 return encodeString(string,ENCODING);
795 }
796
797
798
799
800
801
802 public static String encodeString(String string,String charset)
803 {
804 if (charset==null)
805 charset=ENCODING;
806 byte[] bytes=null;
807 try
808 {
809 bytes=string.getBytes(charset);
810 }
811 catch(UnsupportedEncodingException e)
812 {
813
814 bytes=string.getBytes();
815 }
816
817 int len=bytes.length;
818 byte[] encoded= new byte[bytes.length*3];
819 int n=0;
820 boolean noEncode=true;
821
822 for (int i=0;i<len;i++)
823 {
824 byte b = bytes[i];
825
826 if (b==' ')
827 {
828 noEncode=false;
829 encoded[n++]=(byte)'+';
830 }
831 else if (b>='a' && b<='z' ||
832 b>='A' && b<='Z' ||
833 b>='0' && b<='9')
834 {
835 encoded[n++]=b;
836 }
837 else
838 {
839 noEncode=false;
840 encoded[n++]=(byte)'%';
841 byte nibble= (byte) ((b&0xf0)>>4);
842 if (nibble>=10)
843 encoded[n++]=(byte)('A'+nibble-10);
844 else
845 encoded[n++]=(byte)('0'+nibble);
846 nibble= (byte) (b&0xf);
847 if (nibble>=10)
848 encoded[n++]=(byte)('A'+nibble-10);
849 else
850 encoded[n++]=(byte)('0'+nibble);
851 }
852 }
853
854 if (noEncode)
855 return string;
856
857 try
858 {
859 return new String(encoded,0,n,charset);
860 }
861 catch(UnsupportedEncodingException e)
862 {
863
864 return new String(encoded,0,n);
865 }
866 }
867
868
869
870
871
872 @Override
873 public Object clone()
874 {
875 return new UrlEncoded(this);
876 }
877 }