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 try
466 {
467 switch ((char) b)
468 {
469 case '&':
470 value = buffer.length()==0?"":buffer.toString();
471 buffer.reset();
472 if (key != null)
473 {
474 map.add(key,value);
475 }
476 else if (value!=null&&value.length()>0)
477 {
478 map.add(value,"");
479 }
480 key = null;
481 value=null;
482 if (maxKeys>0 && map.size()>maxKeys)
483 {
484 LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
485 return;
486 }
487 break;
488
489 case '=':
490 if (key!=null)
491 {
492 buffer.append((byte)b);
493 break;
494 }
495 key = buffer.toString();
496 buffer.reset();
497 break;
498
499 case '+':
500 buffer.append((byte)' ');
501 break;
502
503 case '%':
504 int dh=in.read();
505 int dl=in.read();
506 if (dh<0||dl<0)
507 break;
508 buffer.append((byte)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
509 break;
510 default:
511 buffer.append((byte)b);
512 break;
513 }
514 }
515 catch(NotUtf8Exception e)
516 {
517 LOG.warn(e.toString());
518 LOG.debug(e);
519 }
520 if (maxLength>=0 && (++totalLength > maxLength))
521 throw new IllegalStateException("Form too large");
522 }
523
524 if (key != null)
525 {
526 value = buffer.length()==0?"":buffer.toString();
527 buffer.reset();
528 map.add(key,value);
529 }
530 else if (buffer.length()>0)
531 {
532 map.add(buffer.toString(), "");
533 }
534 }
535 }
536
537
538 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
539 {
540 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
541 StringWriter buf = new StringWriter(8192);
542 IO.copy(input,buf,maxLength);
543
544 decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys);
545 }
546
547
548
549
550
551 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
552 throws IOException
553 {
554
555 if (charset==null)
556 {
557 charset=ENCODING;
558 }
559
560 if (StringUtil.__UTF8.equalsIgnoreCase(charset))
561 {
562 decodeUtf8To(in,map,maxLength,maxKeys);
563 return;
564 }
565
566 if (StringUtil.__ISO_8859_1.equals(charset))
567 {
568 decode88591To(in,map,maxLength,maxKeys);
569 return;
570 }
571
572 if (StringUtil.__UTF16.equalsIgnoreCase(charset))
573 {
574 decodeUtf16To(in,map,maxLength,maxKeys);
575 return;
576 }
577
578
579 synchronized(map)
580 {
581 String key = null;
582 String value = null;
583
584 int c;
585 int digit=0;
586 int digits=0;
587
588 int totalLength = 0;
589 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
590
591 int size=0;
592
593 while ((c=in.read())>0)
594 {
595 switch ((char) c)
596 {
597 case '&':
598 size=output.size();
599 value = size==0?"":output.toString(charset);
600 output.setCount(0);
601 if (key != null)
602 {
603 map.add(key,value);
604 }
605 else if (value!=null&&value.length()>0)
606 {
607 map.add(value,"");
608 }
609 key = null;
610 value=null;
611 break;
612 case '=':
613 if (key!=null)
614 {
615 output.write(c);
616 break;
617 }
618 size=output.size();
619 key = size==0?"":output.toString(charset);
620 output.setCount(0);
621 break;
622 case '+':
623 output.write(' ');
624 break;
625 case '%':
626 digits=2;
627 break;
628 default:
629 if (digits==2)
630 {
631 digit=TypeUtil.convertHexDigit((byte)c);
632 digits=1;
633 }
634 else if (digits==1)
635 {
636 output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c));
637 digits=0;
638 }
639 else
640 output.write(c);
641 break;
642 }
643
644 totalLength++;
645 if (maxLength>=0 && totalLength > maxLength)
646 throw new IllegalStateException("Form too large");
647 }
648
649 size=output.size();
650 if (key != null)
651 {
652 value = size==0?"":output.toString(charset);
653 output.setCount(0);
654 map.add(key,value);
655 }
656 else if (size>0)
657 map.add(output.toString(charset),"");
658 }
659 }
660
661
662
663
664
665
666 public static String decodeString(String encoded,int offset,int length,String charset)
667 {
668 if (charset==null || StringUtil.isUTF8(charset))
669 {
670 Utf8StringBuffer buffer=null;
671
672 for (int i=0;i<length;i++)
673 {
674 char c = encoded.charAt(offset+i);
675 if (c<0||c>0xff)
676 {
677 if (buffer==null)
678 {
679 buffer=new Utf8StringBuffer(length);
680 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
681 }
682 else
683 buffer.getStringBuffer().append(c);
684 }
685 else if (c=='+')
686 {
687 if (buffer==null)
688 {
689 buffer=new Utf8StringBuffer(length);
690 buffer.getStringBuffer().append(encoded,offset,offset+i);
691 }
692
693 buffer.getStringBuffer().append(' ');
694 }
695 else if (c=='%' && (i+2)<length)
696 {
697 if (buffer==null)
698 {
699 buffer=new Utf8StringBuffer(length);
700 buffer.getStringBuffer().append(encoded,offset,offset+i);
701 }
702
703 try
704 {
705 byte b=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
706 buffer.append(b);
707 i+=2;
708 }
709 catch(NumberFormatException nfe)
710 {
711 buffer.getStringBuffer().append('%');
712 }
713 }
714 else if (buffer!=null)
715 buffer.getStringBuffer().append(c);
716 }
717
718 if (buffer==null)
719 {
720 if (offset==0 && encoded.length()==length)
721 return encoded;
722 return encoded.substring(offset,offset+length);
723 }
724
725 return buffer.toString();
726 }
727 else
728 {
729 StringBuffer buffer=null;
730
731 try
732 {
733 for (int i=0;i<length;i++)
734 {
735 char c = encoded.charAt(offset+i);
736 if (c<0||c>0xff)
737 {
738 if (buffer==null)
739 {
740 buffer=new StringBuffer(length);
741 buffer.append(encoded,offset,offset+i+1);
742 }
743 else
744 buffer.append(c);
745 }
746 else if (c=='+')
747 {
748 if (buffer==null)
749 {
750 buffer=new StringBuffer(length);
751 buffer.append(encoded,offset,offset+i);
752 }
753
754 buffer.append(' ');
755 }
756 else if (c=='%' && (i+2)<length)
757 {
758 if (buffer==null)
759 {
760 buffer=new StringBuffer(length);
761 buffer.append(encoded,offset,offset+i);
762 }
763
764 byte[] ba=new byte[length];
765 int n=0;
766 while(c>=0 && c<=0xff)
767 {
768 if (c=='%')
769 {
770 if(i+2<length)
771 {
772 try
773 {
774 ba[n++]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
775 i+=3;
776 }
777 catch(NumberFormatException nfe)
778 {
779 ba[n-1] = (byte)'%';
780 for(char next; ((next=encoded.charAt(++i+offset))!='%');)
781 ba[n++] = (byte)(next=='+' ? ' ' : next);
782 }
783 }
784 else
785 {
786 ba[n++] = (byte)'%';
787 i++;
788 }
789 }
790 else if (c=='+')
791 {
792 ba[n++]=(byte)' ';
793 i++;
794 }
795 else
796 {
797 ba[n++]=(byte)c;
798 i++;
799 }
800
801 if (i>=length)
802 break;
803 c = encoded.charAt(offset+i);
804 }
805
806 i--;
807 buffer.append(new String(ba,0,n,charset));
808
809 }
810 else if (buffer!=null)
811 buffer.append(c);
812 }
813
814 if (buffer==null)
815 {
816 if (offset==0 && encoded.length()==length)
817 return encoded;
818 return encoded.substring(offset,offset+length);
819 }
820
821 return buffer.toString();
822 }
823 catch (UnsupportedEncodingException e)
824 {
825 throw new RuntimeException(e);
826 }
827 }
828
829 }
830
831
832
833
834
835
836 public static String encodeString(String string)
837 {
838 return encodeString(string,ENCODING);
839 }
840
841
842
843
844
845
846 public static String encodeString(String string,String charset)
847 {
848 if (charset==null)
849 charset=ENCODING;
850 byte[] bytes=null;
851 try
852 {
853 bytes=string.getBytes(charset);
854 }
855 catch(UnsupportedEncodingException e)
856 {
857
858 bytes=string.getBytes();
859 }
860
861 int len=bytes.length;
862 byte[] encoded= new byte[bytes.length*3];
863 int n=0;
864 boolean noEncode=true;
865
866 for (int i=0;i<len;i++)
867 {
868 byte b = bytes[i];
869
870 if (b==' ')
871 {
872 noEncode=false;
873 encoded[n++]=(byte)'+';
874 }
875 else if (b>='a' && b<='z' ||
876 b>='A' && b<='Z' ||
877 b>='0' && b<='9')
878 {
879 encoded[n++]=b;
880 }
881 else
882 {
883 noEncode=false;
884 encoded[n++]=(byte)'%';
885 byte nibble= (byte) ((b&0xf0)>>4);
886 if (nibble>=10)
887 encoded[n++]=(byte)('A'+nibble-10);
888 else
889 encoded[n++]=(byte)('0'+nibble);
890 nibble= (byte) (b&0xf);
891 if (nibble>=10)
892 encoded[n++]=(byte)('A'+nibble-10);
893 else
894 encoded[n++]=(byte)('0'+nibble);
895 }
896 }
897
898 if (noEncode)
899 return string;
900
901 try
902 {
903 return new String(encoded,0,n,charset);
904 }
905 catch(UnsupportedEncodingException e)
906 {
907
908 return new String(encoded,0,n);
909 }
910 }
911
912
913
914
915
916 @Override
917 public Object clone()
918 {
919 return new UrlEncoded(this);
920 }
921 }