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.length()==0?"":buffer.toString();
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.toString();
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.length()==0?"":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.length()==0?"":buffer.toString();
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.toString();
536 buffer.reset();
537 break;
538
539 case '+':
540 buffer.append((byte)' ');
541 break;
542
543 case '%':
544 int code0=in.read();
545 if ('u'==code0)
546 {
547 int code1=in.read();
548 if (code1>=0)
549 {
550 int code2=in.read();
551 if (code2>=0)
552 {
553 int code3=in.read();
554 if (code3>=0)
555 buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
556 }
557 }
558 }
559 else if (code0>=0)
560 {
561 int code1=in.read();
562 if (code1>=0)
563 buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
564 }
565 break;
566
567 default:
568 buffer.append((byte)b);
569 break;
570 }
571 }
572 catch(NotUtf8Exception e)
573 {
574 LOG.warn(e.toString());
575 LOG.debug(e);
576 }
577 catch(NumberFormatException e)
578 {
579 buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
580 LOG.warn(e.toString());
581 LOG.debug(e);
582 }
583 if (maxLength>=0 && (++totalLength > maxLength))
584 throw new IllegalStateException("Form too large");
585 }
586
587 if (key != null)
588 {
589 value = buffer.length()==0?"":buffer.toString();
590 buffer.reset();
591 map.add(key,value);
592 }
593 else if (buffer.length()>0)
594 {
595 map.add(buffer.toString(), "");
596 }
597 }
598 }
599
600
601 public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
602 {
603 InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16);
604 StringWriter buf = new StringWriter(8192);
605 IO.copy(input,buf,maxLength);
606
607 decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,maxKeys);
608 }
609
610
611
612
613
614 public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
615 throws IOException
616 {
617 if (charset==null)
618 {
619 if (ENCODING.equals(StandardCharsets.UTF_8))
620 decodeUtf8To(in,map,maxLength,maxKeys);
621 else
622 decodeTo(in,map,ENCODING,maxLength,maxKeys);
623 }
624 else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
625 decodeUtf8To(in,map,maxLength,maxKeys);
626 else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
627 decode88591To(in,map,maxLength,maxKeys);
628 else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
629 decodeUtf16To(in,map,maxLength,maxKeys);
630 else
631 decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
632 }
633
634
635
636
637
638 public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
639 throws IOException
640 {
641
642 if (charset==null)
643 charset=ENCODING;
644
645 if (StandardCharsets.UTF_8.equals(charset))
646 {
647 decodeUtf8To(in,map,maxLength,maxKeys);
648 return;
649 }
650
651 if (StandardCharsets.ISO_8859_1.equals(charset))
652 {
653 decode88591To(in,map,maxLength,maxKeys);
654 return;
655 }
656
657 if (StandardCharsets.UTF_16.equals(charset))
658 {
659 decodeUtf16To(in,map,maxLength,maxKeys);
660 return;
661 }
662
663 synchronized(map)
664 {
665 String key = null;
666 String value = null;
667
668 int c;
669
670 int totalLength = 0;
671 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
672
673 int size=0;
674
675 while ((c=in.read())>0)
676 {
677 switch ((char) c)
678 {
679 case '&':
680 size=output.size();
681 value = size==0?"":output.toString(charset);
682 output.setCount(0);
683 if (key != null)
684 {
685 map.add(key,value);
686 }
687 else if (value!=null&&value.length()>0)
688 {
689 map.add(value,"");
690 }
691 key = null;
692 value=null;
693 if (maxKeys>0 && map.size()>maxKeys)
694 throw new IllegalStateException("Form too many keys");
695 break;
696 case '=':
697 if (key!=null)
698 {
699 output.write(c);
700 break;
701 }
702 size=output.size();
703 key = size==0?"":output.toString(charset);
704 output.setCount(0);
705 break;
706 case '+':
707 output.write(' ');
708 break;
709 case '%':
710 int code0=in.read();
711 if ('u'==code0)
712 {
713 int code1=in.read();
714 if (code1>=0)
715 {
716 int code2=in.read();
717 if (code2>=0)
718 {
719 int code3=in.read();
720 if (code3>=0)
721 output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
722 }
723 }
724
725 }
726 else if (code0>=0)
727 {
728 int code1=in.read();
729 if (code1>=0)
730 output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
731 }
732 break;
733 default:
734 output.write(c);
735 break;
736 }
737
738 totalLength++;
739 if (maxLength>=0 && totalLength > maxLength)
740 throw new IllegalStateException("Form too large");
741 }
742
743 size=output.size();
744 if (key != null)
745 {
746 value = size==0?"":output.toString(charset);
747 output.setCount(0);
748 map.add(key,value);
749 }
750 else if (size>0)
751 map.add(output.toString(charset),"");
752 }
753 }
754
755
756
757
758
759
760 public static String decodeString(String encoded,int offset,int length,Charset charset)
761 {
762 if (charset==null || StandardCharsets.UTF_8.equals(charset))
763 {
764 Utf8StringBuffer buffer=null;
765
766 for (int i=0;i<length;i++)
767 {
768 char c = encoded.charAt(offset+i);
769 if (c<0||c>0xff)
770 {
771 if (buffer==null)
772 {
773 buffer=new Utf8StringBuffer(length);
774 buffer.getStringBuffer().append(encoded,offset,offset+i+1);
775 }
776 else
777 buffer.getStringBuffer().append(c);
778 }
779 else if (c=='+')
780 {
781 if (buffer==null)
782 {
783 buffer=new Utf8StringBuffer(length);
784 buffer.getStringBuffer().append(encoded,offset,offset+i);
785 }
786
787 buffer.getStringBuffer().append(' ');
788 }
789 else if (c=='%')
790 {
791 if (buffer==null)
792 {
793 buffer=new Utf8StringBuffer(length);
794 buffer.getStringBuffer().append(encoded,offset,offset+i);
795 }
796
797 if ((i+2)<length)
798 {
799 try
800 {
801 if ('u'==encoded.charAt(offset+i+1))
802 {
803 if((i+5)<length)
804 {
805 int o=offset+i+2;
806 i+=5;
807 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
808 buffer.getStringBuffer().append(unicode);
809 }
810 else
811 {
812 i=length;
813 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
814 }
815 }
816 else
817 {
818 int o=offset+i+1;
819 i+=2;
820 byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
821 buffer.append(b);
822 }
823 }
824 catch(NotUtf8Exception e)
825 {
826 LOG.warn(e.toString());
827 LOG.debug(e);
828 }
829 catch(NumberFormatException e)
830 {
831 LOG.warn(e.toString());
832 LOG.debug(e);
833 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
834 }
835 }
836 else
837 {
838 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
839 i=length;
840 }
841 }
842 else if (buffer!=null)
843 buffer.getStringBuffer().append(c);
844 }
845
846 if (buffer==null)
847 {
848 if (offset==0 && encoded.length()==length)
849 return encoded;
850 return encoded.substring(offset,offset+length);
851 }
852
853 return buffer.toReplacedString();
854 }
855 else
856 {
857 StringBuffer buffer=null;
858
859 for (int i=0;i<length;i++)
860 {
861 char c = encoded.charAt(offset+i);
862 if (c<0||c>0xff)
863 {
864 if (buffer==null)
865 {
866 buffer=new StringBuffer(length);
867 buffer.append(encoded,offset,offset+i+1);
868 }
869 else
870 buffer.append(c);
871 }
872 else if (c=='+')
873 {
874 if (buffer==null)
875 {
876 buffer=new StringBuffer(length);
877 buffer.append(encoded,offset,offset+i);
878 }
879
880 buffer.append(' ');
881 }
882 else if (c=='%')
883 {
884 if (buffer==null)
885 {
886 buffer=new StringBuffer(length);
887 buffer.append(encoded,offset,offset+i);
888 }
889
890 byte[] ba=new byte[length];
891 int n=0;
892 while(c>=0 && c<=0xff)
893 {
894 if (c=='%')
895 {
896 if(i+2<length)
897 {
898 try
899 {
900 if ('u'==encoded.charAt(offset+i+1))
901 {
902 if (i+6<length)
903 {
904 int o=offset+i+2;
905 i+=6;
906 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
907 byte[] reencoded = unicode.getBytes(charset);
908 System.arraycopy(reencoded,0,ba,n,reencoded.length);
909 n+=reencoded.length;
910 }
911 else
912 {
913 ba[n++] = (byte)'?';
914 i=length;
915 }
916 }
917 else
918 {
919 int o=offset+i+1;
920 i+=3;
921 ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
922 n++;
923 }
924 }
925 catch(Exception e)
926 {
927 LOG.warn(e.toString());
928 LOG.debug(e);
929 ba[n++] = (byte)'?';
930 }
931 }
932 else
933 {
934 ba[n++] = (byte)'?';
935 i=length;
936 }
937 }
938 else if (c=='+')
939 {
940 ba[n++]=(byte)' ';
941 i++;
942 }
943 else
944 {
945 ba[n++]=(byte)c;
946 i++;
947 }
948
949 if (i>=length)
950 break;
951 c = encoded.charAt(offset+i);
952 }
953
954 i--;
955 buffer.append(new String(ba,0,n,charset));
956
957 }
958 else if (buffer!=null)
959 buffer.append(c);
960 }
961
962 if (buffer==null)
963 {
964 if (offset==0 && encoded.length()==length)
965 return encoded;
966 return encoded.substring(offset,offset+length);
967 }
968
969 return buffer.toString();
970 }
971
972 }
973
974
975
976
977
978
979 public static String encodeString(String string)
980 {
981 return encodeString(string,ENCODING);
982 }
983
984
985
986
987
988
989 public static String encodeString(String string,Charset charset)
990 {
991 if (charset==null)
992 charset=ENCODING;
993 byte[] bytes=null;
994 bytes=string.getBytes(charset);
995
996 int len=bytes.length;
997 byte[] encoded= new byte[bytes.length*3];
998 int n=0;
999 boolean noEncode=true;
1000
1001 for (int i=0;i<len;i++)
1002 {
1003 byte b = bytes[i];
1004
1005 if (b==' ')
1006 {
1007 noEncode=false;
1008 encoded[n++]=(byte)'+';
1009 }
1010 else if (b>='a' && b<='z' ||
1011 b>='A' && b<='Z' ||
1012 b>='0' && b<='9')
1013 {
1014 encoded[n++]=b;
1015 }
1016 else
1017 {
1018 noEncode=false;
1019 encoded[n++]=(byte)'%';
1020 byte nibble= (byte) ((b&0xf0)>>4);
1021 if (nibble>=10)
1022 encoded[n++]=(byte)('A'+nibble-10);
1023 else
1024 encoded[n++]=(byte)('0'+nibble);
1025 nibble= (byte) (b&0xf);
1026 if (nibble>=10)
1027 encoded[n++]=(byte)('A'+nibble-10);
1028 else
1029 encoded[n++]=(byte)('0'+nibble);
1030 }
1031 }
1032
1033 if (noEncode)
1034 return string;
1035
1036 return new String(encoded,0,n,charset);
1037 }
1038
1039
1040
1041
1042
1043 @Override
1044 public Object clone()
1045 {
1046 return new UrlEncoded(this);
1047 }
1048 }