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 public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys)
364 throws IOException
365 {
366 synchronized(map)
367 {
368 StringBuffer buffer = new StringBuffer();
369 String key = null;
370 String value = null;
371
372 int b;
373
374
375 int totalLength=0;
376 while ((b=in.read())>=0)
377 {
378 switch ((char) b)
379 {
380 case '&':
381 value = buffer.length()==0?"":buffer.toString();
382 buffer.setLength(0);
383 if (key != null)
384 {
385 map.add(key,value);
386 }
387 else if (value!=null&&value.length()>0)
388 {
389 map.add(value,"");
390 }
391 key = null;
392 value=null;
393 if (maxKeys>0 && map.size()>maxKeys)
394 {
395 LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
396 return;
397 }
398 break;
399
400 case '=':
401 if (key!=null)
402 {
403 buffer.append((char)b);
404 break;
405 }
406 key = buffer.toString();
407 buffer.setLength(0);
408 break;
409
410 case '+':
411 buffer.append(' ');
412 break;
413
414 case '%':
415 int dh=in.read();
416 int dl=in.read();
417 if (dh<0||dl<0)
418 break;
419 buffer.append((char)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
420 break;
421 default:
422 buffer.append((char)b);
423 break;
424 }
425 if (maxLength>=0 && (++totalLength > maxLength))
426 throw new IllegalStateException("Form too large");
427 }
428
429 if (key != null)
430 {
431 value = buffer.length()==0?"":buffer.toString();
432 buffer.setLength(0);
433 map.add(key,value);
434 }
435 else if (buffer.length()>0)
436 {
437 map.add(buffer.toString(), "");
438 }
439 }
440 }
441
442
443
444
445
446
447
448 public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys)
449 throws IOException
450 {
451 synchronized(map)
452 {
453 Utf8StringBuilder buffer = new Utf8StringBuilder();
454 String key = null;
455 String value = null;
456
457 int b;
458
459
460 int totalLength=0;
461 while ((b=in.read())>=0)
462 {
463 try
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 }
513 catch(NotUtf8Exception e)
514 {
515 LOG.warn(e.toString());
516 LOG.debug(e);
517 }
518 if (maxLength>=0 && (++totalLength > maxLength))
519 throw new IllegalStateException("Form too large");
520 }
521
522 if (key != null)
523 {
524 value = buffer.length()==0?"":buffer.toString();
525 buffer.reset();
526 map.add(key,value);
527 }
528 else if (buffer.length()>0)
529 {
530 map.add(buffer.toString(), "");
531 }
532 }
533 }
534
535
536 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
537 {
538 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
539 StringWriter buf = new StringWriter(8192);
540 IO.copy(input,buf,maxLength);
541
542 decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys);
543 }
544
545
546
547
548
549 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
550 throws IOException
551 {
552
553 if (charset==null)
554 {
555 charset=ENCODING;
556 }
557
558 if (StringUtil.__UTF8.equalsIgnoreCase(charset))
559 {
560 decodeUtf8To(in,map,maxLength,maxKeys);
561 return;
562 }
563
564 if (StringUtil.__ISO_8859_1.equals(charset))
565 {
566 decode88591To(in,map,maxLength,maxKeys);
567 return;
568 }
569
570 if (StringUtil.__UTF16.equalsIgnoreCase(charset))
571 {
572 decodeUtf16To(in,map,maxLength,maxKeys);
573 return;
574 }
575
576
577 synchronized(map)
578 {
579 String key = null;
580 String value = null;
581
582 int c;
583 int digit=0;
584 int digits=0;
585
586 int totalLength = 0;
587 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
588
589 int size=0;
590
591 while ((c=in.read())>0)
592 {
593 switch ((char) c)
594 {
595 case '&':
596 size=output.size();
597 value = size==0?"":output.toString(charset);
598 output.setCount(0);
599 if (key != null)
600 {
601 map.add(key,value);
602 }
603 else if (value!=null&&value.length()>0)
604 {
605 map.add(value,"");
606 }
607 key = null;
608 value=null;
609 break;
610 case '=':
611 if (key!=null)
612 {
613 output.write(c);
614 break;
615 }
616 size=output.size();
617 key = size==0?"":output.toString(charset);
618 output.setCount(0);
619 break;
620 case '+':
621 output.write(' ');
622 break;
623 case '%':
624 digits=2;
625 break;
626 default:
627 if (digits==2)
628 {
629 digit=TypeUtil.convertHexDigit((byte)c);
630 digits=1;
631 }
632 else if (digits==1)
633 {
634 output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c));
635 digits=0;
636 }
637 else
638 output.write(c);
639 break;
640 }
641
642 totalLength++;
643 if (maxLength>=0 && totalLength > maxLength)
644 throw new IllegalStateException("Form too large");
645 }
646
647 size=output.size();
648 if (key != null)
649 {
650 value = size==0?"":output.toString(charset);
651 output.setCount(0);
652 map.add(key,value);
653 }
654 else if (size>0)
655 map.add(output.toString(charset),"");
656 }
657 }
658
659
660
661
662
663
664 public static String decodeString(String encoded,int offset,int length,String charset)
665 {
666 if (charset==null || StringUtil.isUTF8(charset))
667 {
668 Utf8StringBuffer buffer=null;
669
670 for (int i=0;i<length;i++)
671 {
672 char c = encoded.charAt(offset+i);
673 if (c<0||c>0xff)
674 {
675 if (buffer==null)
676 {
677 buffer=new Utf8StringBuffer(length);
678 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
679 }
680 else
681 buffer.getStringBuffer().append(c);
682 }
683 else if (c=='+')
684 {
685 if (buffer==null)
686 {
687 buffer=new Utf8StringBuffer(length);
688 buffer.getStringBuffer().append(encoded,offset,offset+i);
689 }
690
691 buffer.getStringBuffer().append(' ');
692 }
693 else if (c=='%' && (i+2)<length)
694 {
695 if (buffer==null)
696 {
697 buffer=new Utf8StringBuffer(length);
698 buffer.getStringBuffer().append(encoded,offset,offset+i);
699 }
700
701 try
702 {
703 byte b=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
704 buffer.append(b);
705 i+=2;
706 }
707 catch(NumberFormatException nfe)
708 {
709 buffer.getStringBuffer().append('%');
710 }
711 }
712 else if (buffer!=null)
713 buffer.getStringBuffer().append(c);
714 }
715
716 if (buffer==null)
717 {
718 if (offset==0 && encoded.length()==length)
719 return encoded;
720 return encoded.substring(offset,offset+length);
721 }
722
723 return buffer.toString();
724 }
725 else
726 {
727 StringBuffer buffer=null;
728
729 try
730 {
731 for (int i=0;i<length;i++)
732 {
733 char c = encoded.charAt(offset+i);
734 if (c<0||c>0xff)
735 {
736 if (buffer==null)
737 {
738 buffer=new StringBuffer(length);
739 buffer.append(encoded,offset,offset+i+1);
740 }
741 else
742 buffer.append(c);
743 }
744 else if (c=='+')
745 {
746 if (buffer==null)
747 {
748 buffer=new StringBuffer(length);
749 buffer.append(encoded,offset,offset+i);
750 }
751
752 buffer.append(' ');
753 }
754 else if (c=='%' && (i+2)<length)
755 {
756 if (buffer==null)
757 {
758 buffer=new StringBuffer(length);
759 buffer.append(encoded,offset,offset+i);
760 }
761
762 byte[] ba=new byte[length];
763 int n=0;
764 while(c>=0 && c<=0xff)
765 {
766 if (c=='%')
767 {
768 if(i+2<length)
769 {
770 try
771 {
772 ba[n]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
773 n++;
774 i+=3;
775 }
776 catch(NumberFormatException nfe)
777 {
778 LOG.ignore(nfe);
779 ba[n++] = (byte)'%';
780 i++;
781 }
782 }
783 else
784 {
785 ba[n++] = (byte)'%';
786 i++;
787 }
788 }
789 else if (c=='+')
790 {
791 ba[n++]=(byte)' ';
792 i++;
793 }
794 else
795 {
796 ba[n++]=(byte)c;
797 i++;
798 }
799
800 if (i>=length)
801 break;
802 c = encoded.charAt(offset+i);
803 }
804
805 i--;
806 buffer.append(new String(ba,0,n,charset));
807
808 }
809 else if (buffer!=null)
810 buffer.append(c);
811 }
812
813 if (buffer==null)
814 {
815 if (offset==0 && encoded.length()==length)
816 return encoded;
817 return encoded.substring(offset,offset+length);
818 }
819
820 return buffer.toString();
821 }
822 catch (UnsupportedEncodingException e)
823 {
824 throw new RuntimeException(e);
825 }
826 }
827
828 }
829
830
831
832
833
834
835 public static String encodeString(String string)
836 {
837 return encodeString(string,ENCODING);
838 }
839
840
841
842
843
844
845 public static String encodeString(String string,String charset)
846 {
847 if (charset==null)
848 charset=ENCODING;
849 byte[] bytes=null;
850 try
851 {
852 bytes=string.getBytes(charset);
853 }
854 catch(UnsupportedEncodingException e)
855 {
856
857 bytes=string.getBytes();
858 }
859
860 int len=bytes.length;
861 byte[] encoded= new byte[bytes.length*3];
862 int n=0;
863 boolean noEncode=true;
864
865 for (int i=0;i<len;i++)
866 {
867 byte b = bytes[i];
868
869 if (b==' ')
870 {
871 noEncode=false;
872 encoded[n++]=(byte)'+';
873 }
874 else if (b>='a' && b<='z' ||
875 b>='A' && b<='Z' ||
876 b>='0' && b<='9')
877 {
878 encoded[n++]=b;
879 }
880 else
881 {
882 noEncode=false;
883 encoded[n++]=(byte)'%';
884 byte nibble= (byte) ((b&0xf0)>>4);
885 if (nibble>=10)
886 encoded[n++]=(byte)('A'+nibble-10);
887 else
888 encoded[n++]=(byte)('0'+nibble);
889 nibble= (byte) (b&0xf);
890 if (nibble>=10)
891 encoded[n++]=(byte)('A'+nibble-10);
892 else
893 encoded[n++]=(byte)('0'+nibble);
894 }
895 }
896
897 if (noEncode)
898 return string;
899
900 try
901 {
902 return new String(encoded,0,n,charset);
903 }
904 catch(UnsupportedEncodingException e)
905 {
906
907 return new String(encoded,0,n);
908 }
909 }
910
911
912
913
914
915 @Override
916 public Object clone()
917 {
918 return new UrlEncoded(this);
919 }
920 }