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