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 {
327 byte top=raw[++i];
328 byte hi=raw[++i];
329 byte lo=raw[++i];
330 byte bot=raw[++i];
331 buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
332 }
333 else
334 {
335 buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
336 i=end;
337 }
338 }
339 else
340 {
341 byte hi=raw[++i];
342 byte lo=raw[++i];
343 buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
344 }
345 }
346 else
347 {
348 buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
349 i=end;
350 }
351 break;
352
353 default:
354 buffer.append(b);
355 break;
356 }
357 }
358 catch(NotUtf8Exception e)
359 {
360 LOG.warn(e.toString());
361 LOG.debug(e);
362 }
363 catch(NumberFormatException e)
364 {
365 buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
366 LOG.warn(e.toString());
367 LOG.debug(e);
368 }
369 }
370
371 if (key != null)
372 {
373 value = buffer.length()==0?"":buffer.toReplacedString();
374 buffer.reset();
375 map.add(key,value);
376 }
377 else if (buffer.length()>0)
378 {
379 map.add(buffer.toReplacedString(),"");
380 }
381 }
382 }
383
384
385
386
387
388
389
390 public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
391 throws IOException
392 {
393 synchronized(map)
394 {
395 StringBuffer buffer = new StringBuffer();
396 String key = null;
397 String value = null;
398
399 int b;
400
401 int totalLength=0;
402 while ((b=in.read())>=0)
403 {
404 switch ((char) b)
405 {
406 case '&':
407 value = buffer.length()==0?"":buffer.toString();
408 buffer.setLength(0);
409 if (key != null)
410 {
411 map.add(key,value);
412 }
413 else if (value!=null&&value.length()>0)
414 {
415 map.add(value,"");
416 }
417 key = null;
418 value=null;
419 if (maxKeys>0 && map.size()>maxKeys)
420 throw new IllegalStateException("Form too many keys");
421 break;
422
423 case '=':
424 if (key!=null)
425 {
426 buffer.append((char)b);
427 break;
428 }
429 key = buffer.toString();
430 buffer.setLength(0);
431 break;
432
433 case '+':
434 buffer.append(' ');
435 break;
436
437 case '%':
438 int code0=in.read();
439 if ('u'==code0)
440 {
441 int code1=in.read();
442 if (code1>=0)
443 {
444 int code2=in.read();
445 if (code2>=0)
446 {
447 int code3=in.read();
448 if (code3>=0)
449 buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
450 }
451 }
452 }
453 else if (code0>=0)
454 {
455 int code1=in.read();
456 if (code1>=0)
457 buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
458 }
459 break;
460
461 default:
462 buffer.append((char)b);
463 break;
464 }
465 if (maxLength>=0 && (++totalLength > maxLength))
466 throw new IllegalStateException("Form too large");
467 }
468
469 if (key != null)
470 {
471 value = buffer.length()==0?"":buffer.toString();
472 buffer.setLength(0);
473 map.add(key,value);
474 }
475 else if (buffer.length()>0)
476 {
477 map.add(buffer.toString(), "");
478 }
479 }
480 }
481
482
483
484
485
486
487
488 public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
489 throws IOException
490 {
491 synchronized(map)
492 {
493 Utf8StringBuilder buffer = new Utf8StringBuilder();
494 String key = null;
495 String value = null;
496
497 int b;
498
499 int totalLength=0;
500 while ((b=in.read())>=0)
501 {
502 try
503 {
504 switch ((char) b)
505 {
506 case '&':
507 value = buffer.length()==0?"":buffer.toString();
508 buffer.reset();
509 if (key != null)
510 {
511 map.add(key,value);
512 }
513 else if (value!=null&&value.length()>0)
514 {
515 map.add(value,"");
516 }
517 key = null;
518 value=null;
519 if (maxKeys>0 && map.size()>maxKeys)
520 throw new IllegalStateException("Form too many keys");
521 break;
522
523 case '=':
524 if (key!=null)
525 {
526 buffer.append((byte)b);
527 break;
528 }
529 key = buffer.toString();
530 buffer.reset();
531 break;
532
533 case '+':
534 buffer.append((byte)' ');
535 break;
536
537 case '%':
538 int code0=in.read();
539 if ('u'==code0)
540 {
541 int code1=in.read();
542 if (code1>=0)
543 {
544 int code2=in.read();
545 if (code2>=0)
546 {
547 int code3=in.read();
548 if (code3>=0)
549 buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
550 }
551 }
552 }
553 else if (code0>=0)
554 {
555 int code1=in.read();
556 if (code1>=0)
557 buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
558 }
559 break;
560
561 default:
562 buffer.append((byte)b);
563 break;
564 }
565 }
566 catch(NotUtf8Exception e)
567 {
568 LOG.warn(e.toString());
569 LOG.debug(e);
570 }
571 catch(NumberFormatException e)
572 {
573 buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
574 LOG.warn(e.toString());
575 LOG.debug(e);
576 }
577 if (maxLength>=0 && (++totalLength > maxLength))
578 throw new IllegalStateException("Form too large");
579 }
580
581 if (key != null)
582 {
583 value = buffer.length()==0?"":buffer.toString();
584 buffer.reset();
585 map.add(key,value);
586 }
587 else if (buffer.length()>0)
588 {
589 map.add(buffer.toString(), "");
590 }
591 }
592 }
593
594
595 public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
596 {
597 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
598 StringWriter buf = new StringWriter(8192);
599 IO.copy(input,buf,maxLength);
600
601 decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys);
602 }
603
604
605
606
607
608 public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
609 throws IOException
610 {
611 if (charset==null)
612 {
613 if (ENCODING==StringUtil.__UTF8_CHARSET)
614 decodeUtf8To(in,map,maxLength,maxKeys);
615 else
616 decodeTo(in,map,ENCODING,maxLength,maxKeys);
617 }
618 else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
619 decodeUtf8To(in,map,maxLength,maxKeys);
620 else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
621 decode88591To(in,map,maxLength,maxKeys);
622 else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
623 decodeUtf16To(in,map,maxLength,maxKeys);
624 else
625 decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
626 }
627
628
629
630
631
632 public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
633 throws IOException
634 {
635
636 if (charset==null)
637 charset=ENCODING;
638
639 if (StringUtil.__UTF8_CHARSET.equals(charset))
640 {
641 decodeUtf8To(in,map,maxLength,maxKeys);
642 return;
643 }
644
645 if (StringUtil.__ISO_8859_1_CHARSET.equals(charset))
646 {
647 decode88591To(in,map,maxLength,maxKeys);
648 return;
649 }
650
651 if (StringUtil.__UTF16_CHARSET.equals(charset))
652 {
653 decodeUtf16To(in,map,maxLength,maxKeys);
654 return;
655 }
656
657 synchronized(map)
658 {
659 String key = null;
660 String value = null;
661
662 int c;
663
664 int totalLength = 0;
665 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
666
667 int size=0;
668
669 while ((c=in.read())>0)
670 {
671 switch ((char) c)
672 {
673 case '&':
674 size=output.size();
675 value = size==0?"":output.toString(charset);
676 output.setCount(0);
677 if (key != null)
678 {
679 map.add(key,value);
680 }
681 else if (value!=null&&value.length()>0)
682 {
683 map.add(value,"");
684 }
685 key = null;
686 value=null;
687 if (maxKeys>0 && map.size()>maxKeys)
688 throw new IllegalStateException("Form too many keys");
689 break;
690 case '=':
691 if (key!=null)
692 {
693 output.write(c);
694 break;
695 }
696 size=output.size();
697 key = size==0?"":output.toString(charset);
698 output.setCount(0);
699 break;
700 case '+':
701 output.write(' ');
702 break;
703 case '%':
704 int code0=in.read();
705 if ('u'==code0)
706 {
707 int code1=in.read();
708 if (code1>=0)
709 {
710 int code2=in.read();
711 if (code2>=0)
712 {
713 int code3=in.read();
714 if (code3>=0)
715 output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
716 }
717 }
718
719 }
720 else if (code0>=0)
721 {
722 int code1=in.read();
723 if (code1>=0)
724 output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
725 }
726 break;
727 default:
728 output.write(c);
729 break;
730 }
731
732 totalLength++;
733 if (maxLength>=0 && totalLength > maxLength)
734 throw new IllegalStateException("Form too large");
735 }
736
737 size=output.size();
738 if (key != null)
739 {
740 value = size==0?"":output.toString(charset);
741 output.setCount(0);
742 map.add(key,value);
743 }
744 else if (size>0)
745 map.add(output.toString(charset),"");
746 }
747 }
748
749
750
751
752
753
754 public static String decodeString(String encoded,int offset,int length,Charset charset)
755 {
756 if (charset==null || StringUtil.__UTF8_CHARSET.equals(charset))
757 {
758 Utf8StringBuffer buffer=null;
759
760 for (int i=0;i<length;i++)
761 {
762 char c = encoded.charAt(offset+i);
763 if (c<0||c>0xff)
764 {
765 if (buffer==null)
766 {
767 buffer=new Utf8StringBuffer(length);
768 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
769 }
770 else
771 buffer.getStringBuffer().append(c);
772 }
773 else if (c=='+')
774 {
775 if (buffer==null)
776 {
777 buffer=new Utf8StringBuffer(length);
778 buffer.getStringBuffer().append(encoded,offset,offset+i);
779 }
780
781 buffer.getStringBuffer().append(' ');
782 }
783 else if (c=='%')
784 {
785 if (buffer==null)
786 {
787 buffer=new Utf8StringBuffer(length);
788 buffer.getStringBuffer().append(encoded,offset,offset+i);
789 }
790
791 if ((i+2)<length)
792 {
793 try
794 {
795 if ('u'==encoded.charAt(offset+i+1))
796 {
797 if((i+5)<length)
798 {
799 int o=offset+i+2;
800 i+=5;
801 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
802 buffer.getStringBuffer().append(unicode);
803 }
804 else
805 {
806 i=length;
807 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
808 }
809 }
810 else
811 {
812 int o=offset+i+1;
813 i+=2;
814 byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
815 buffer.append(b);
816 }
817 }
818 catch(NotUtf8Exception e)
819 {
820 LOG.warn(e.toString());
821 LOG.debug(e);
822 }
823 catch(NumberFormatException e)
824 {
825 LOG.warn(e.toString());
826 LOG.debug(e);
827 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
828 }
829 }
830 else
831 {
832 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
833 i=length;
834 }
835 }
836 else if (buffer!=null)
837 buffer.getStringBuffer().append(c);
838 }
839
840 if (buffer==null)
841 {
842 if (offset==0 && encoded.length()==length)
843 return encoded;
844 return encoded.substring(offset,offset+length);
845 }
846
847 return buffer.toReplacedString();
848 }
849 else
850 {
851 StringBuffer buffer=null;
852
853 for (int i=0;i<length;i++)
854 {
855 char c = encoded.charAt(offset+i);
856 if (c<0||c>0xff)
857 {
858 if (buffer==null)
859 {
860 buffer=new StringBuffer(length);
861 buffer.append(encoded,offset,offset+i+1);
862 }
863 else
864 buffer.append(c);
865 }
866 else if (c=='+')
867 {
868 if (buffer==null)
869 {
870 buffer=new StringBuffer(length);
871 buffer.append(encoded,offset,offset+i);
872 }
873
874 buffer.append(' ');
875 }
876 else if (c=='%')
877 {
878 if (buffer==null)
879 {
880 buffer=new StringBuffer(length);
881 buffer.append(encoded,offset,offset+i);
882 }
883
884 byte[] ba=new byte[length];
885 int n=0;
886 while(c>=0 && c<=0xff)
887 {
888 if (c=='%')
889 {
890 if(i+2<length)
891 {
892 try
893 {
894 if ('u'==encoded.charAt(offset+i+1))
895 {
896 if (i+6<length)
897 {
898 int o=offset+i+2;
899 i+=6;
900 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
901 byte[] reencoded = unicode.getBytes(charset);
902 System.arraycopy(reencoded,0,ba,n,reencoded.length);
903 n+=reencoded.length;
904 }
905 else
906 {
907 ba[n++] = (byte)'?';
908 i=length;
909 }
910 }
911 else
912 {
913 int o=offset+i+1;
914 i+=3;
915 ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
916 n++;
917 }
918 }
919 catch(Exception e)
920 {
921 LOG.warn(e.toString());
922 LOG.debug(e);
923 ba[n++] = (byte)'?';
924 }
925 }
926 else
927 {
928 ba[n++] = (byte)'?';
929 i=length;
930 }
931 }
932 else if (c=='+')
933 {
934 ba[n++]=(byte)' ';
935 i++;
936 }
937 else
938 {
939 ba[n++]=(byte)c;
940 i++;
941 }
942
943 if (i>=length)
944 break;
945 c = encoded.charAt(offset+i);
946 }
947
948 i--;
949 buffer.append(new String(ba,0,n,charset));
950
951 }
952 else if (buffer!=null)
953 buffer.append(c);
954 }
955
956 if (buffer==null)
957 {
958 if (offset==0 && encoded.length()==length)
959 return encoded;
960 return encoded.substring(offset,offset+length);
961 }
962
963 return buffer.toString();
964 }
965
966 }
967
968
969
970
971
972
973 public static String encodeString(String string)
974 {
975 return encodeString(string,ENCODING);
976 }
977
978
979
980
981
982
983 public static String encodeString(String string,Charset charset)
984 {
985 if (charset==null)
986 charset=ENCODING;
987 byte[] bytes=null;
988 bytes=string.getBytes(charset);
989
990 int len=bytes.length;
991 byte[] encoded= new byte[bytes.length*3];
992 int n=0;
993 boolean noEncode=true;
994
995 for (int i=0;i<len;i++)
996 {
997 byte b = bytes[i];
998
999 if (b==' ')
1000 {
1001 noEncode=false;
1002 encoded[n++]=(byte)'+';
1003 }
1004 else if (b>='a' && b<='z' ||
1005 b>='A' && b<='Z' ||
1006 b>='0' && b<='9')
1007 {
1008 encoded[n++]=b;
1009 }
1010 else
1011 {
1012 noEncode=false;
1013 encoded[n++]=(byte)'%';
1014 byte nibble= (byte) ((b&0xf0)>>4);
1015 if (nibble>=10)
1016 encoded[n++]=(byte)('A'+nibble-10);
1017 else
1018 encoded[n++]=(byte)('0'+nibble);
1019 nibble= (byte) (b&0xf);
1020 if (nibble>=10)
1021 encoded[n++]=(byte)('A'+nibble-10);
1022 else
1023 encoded[n++]=(byte)('0'+nibble);
1024 }
1025 }
1026
1027 if (noEncode)
1028 return string;
1029
1030 return new String(encoded,0,n,charset);
1031 }
1032
1033
1034
1035
1036
1037 @Override
1038 public Object clone()
1039 {
1040 return new UrlEncoded(this);
1041 }
1042 }