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