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