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