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