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