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