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