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)
609 charset=StringUtil.__UTF8;
610 byte[] bytes=null;
611 int n=0;
612
613 for (int i=0;i<length;i++)
614 {
615 char c = encoded.charAt(offset+i);
616 if (c<0||c>0xff)
617 throw new IllegalArgumentException("Not encoded");
618
619 if (c=='+')
620 {
621 if (bytes==null)
622 {
623 bytes=new byte[length*2];
624 encoded.getBytes(offset, offset+i, bytes, 0);
625 n=i;
626 }
627 bytes[n++] = (byte) ' ';
628 }
629 else if (c=='%' && (i+2)<length)
630 {
631 byte b;
632 char cn = encoded.charAt(offset+i+1);
633 if (cn>='a' && cn<='z')
634 b=(byte)(10+cn-'a');
635 else if (cn>='A' && cn<='Z')
636 b=(byte)(10+cn-'A');
637 else
638 b=(byte)(cn-'0');
639 cn = encoded.charAt(offset+i+2);
640 if (cn>='a' && cn<='z')
641 b=(byte)(b*16+10+cn-'a');
642 else if (cn>='A' && cn<='Z')
643 b=(byte)(b*16+10+cn-'A');
644 else
645 b=(byte)(b*16+cn-'0');
646
647 if (bytes==null)
648 {
649 bytes=new byte[length];
650 encoded.getBytes(offset, offset+i, bytes, 0);
651 n=i;
652 }
653 i+=2;
654 bytes[n++]=b;
655 }
656 else if (n>0)
657 bytes[n++] = (byte) c;
658 }
659
660 if (bytes==null)
661 {
662 if (offset==0 && encoded.length()==length)
663 return encoded;
664 return encoded.substring(offset,offset+length);
665 }
666
667 try
668 {
669 return new String(bytes,0,n,charset);
670 }
671 catch (UnsupportedEncodingException e)
672 {
673 Log.warn(e.toString());
674 Log.debug(e);
675 return new String(bytes,0,n);
676 }
677
678 }
679
680
681
682
683
684
685
686 public static String encodeString(String string)
687 {
688 return encodeString(string,StringUtil.__UTF8);
689 }
690
691
692
693
694
695
696 public static String encodeString(String string,String charset)
697 {
698 if (charset==null)
699 charset=StringUtil.__UTF8;
700 byte[] bytes=null;
701 try
702 {
703 bytes=string.getBytes(charset);
704 }
705 catch(UnsupportedEncodingException e)
706 {
707
708 bytes=string.getBytes();
709 }
710
711 int len=bytes.length;
712 byte[] encoded= new byte[bytes.length*3];
713 int n=0;
714 boolean noEncode=true;
715
716 for (int i=0;i<len;i++)
717 {
718 byte b = bytes[i];
719
720 if (b==' ')
721 {
722 noEncode=false;
723 encoded[n++]=(byte)'+';
724 }
725 else if (b>='a' && b<='z' ||
726 b>='A' && b<='Z' ||
727 b>='0' && b<='9')
728 {
729 encoded[n++]=b;
730 }
731 else
732 {
733 noEncode=false;
734 encoded[n++]=(byte)'%';
735 byte nibble= (byte) ((b&0xf0)>>4);
736 if (nibble>=10)
737 encoded[n++]=(byte)('A'+nibble-10);
738 else
739 encoded[n++]=(byte)('0'+nibble);
740 nibble= (byte) (b&0xf);
741 if (nibble>=10)
742 encoded[n++]=(byte)('A'+nibble-10);
743 else
744 encoded[n++]=(byte)('0'+nibble);
745 }
746 }
747
748 if (noEncode)
749 return string;
750
751 try
752 {
753 return new String(encoded,0,n,charset);
754 }
755 catch(UnsupportedEncodingException e)
756 {
757
758 return new String(encoded,0,n);
759 }
760 }
761
762
763
764
765
766 public Object clone()
767 {
768 return new UrlEncoded(this);
769 }
770 }