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