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 synchronized(map)
246 {
247 Utf8StringBuffer buffer = new Utf8StringBuffer();
248 String key = null;
249 String value = null;
250
251
252 int end=offset+length;
253 for (int i=offset;i<end;i++)
254 {
255 byte b=raw[i];
256 switch ((char)(0xff&b))
257 {
258 case '&':
259 value = buffer.length()==0?"":buffer.toString();
260 buffer.reset();
261 if (key != null)
262 {
263 map.add(key,value);
264 }
265 else if (value!=null&&value.length()>0)
266 {
267 map.add(value,"");
268 }
269 key = null;
270 value=null;
271 break;
272
273 case '=':
274 if (key!=null)
275 {
276 buffer.append(b);
277 break;
278 }
279 key = buffer.toString();
280 buffer.reset();
281 break;
282
283 case '+':
284 buffer.append((byte)' ');
285 break;
286
287 case '%':
288 if (i+2<end)
289 buffer.append((byte)((TypeUtil.convertHexDigit(raw[++i])<<4) + TypeUtil.convertHexDigit(raw[++i])));
290 break;
291 default:
292 buffer.append(b);
293 break;
294 }
295 }
296
297 if (key != null)
298 {
299 value = buffer.length()==0?"":buffer.toString();
300 buffer.reset();
301 map.add(key,value);
302 }
303 else if (buffer.length()>0)
304 {
305 map.add(buffer.toString(),"");
306 }
307 }
308 }
309
310
311
312
313
314
315
316 public static void decode88591To(InputStream in, MultiMap map, int maxLength)
317 throws IOException
318 {
319 synchronized(map)
320 {
321 StringBuffer buffer = new StringBuffer();
322 String key = null;
323 String value = null;
324
325 int b;
326
327
328 int totalLength=0;
329 while ((b=in.read())>=0)
330 {
331 switch ((char) b)
332 {
333 case '&':
334 value = buffer.length()==0?"":buffer.toString();
335 buffer.setLength(0);
336 if (key != null)
337 {
338 map.add(key,value);
339 }
340 else if (value!=null&&value.length()>0)
341 {
342 map.add(value,"");
343 }
344 key = null;
345 value=null;
346 break;
347
348 case '=':
349 if (key!=null)
350 {
351 buffer.append((char)b);
352 break;
353 }
354 key = buffer.toString();
355 buffer.setLength(0);
356 break;
357
358 case '+':
359 buffer.append((char)' ');
360 break;
361
362 case '%':
363 int dh=in.read();
364 int dl=in.read();
365 if (dh<0||dl<0)
366 break;
367 buffer.append((char)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
368 break;
369 default:
370 buffer.append((char)b);
371 break;
372 }
373 if (maxLength>=0 && (++totalLength > maxLength))
374 throw new IllegalStateException("Form too large");
375 }
376
377 if (key != null)
378 {
379 value = buffer.length()==0?"":buffer.toString();
380 buffer.setLength(0);
381 map.add(key,value);
382 }
383 else if (buffer.length()>0)
384 {
385 map.add(buffer.toString(), "");
386 }
387 }
388 }
389
390
391
392
393
394
395
396 public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength)
397 throws IOException
398 {
399 synchronized(map)
400 {
401 Utf8StringBuffer buffer = new Utf8StringBuffer();
402 String key = null;
403 String value = null;
404
405 int b;
406
407
408 int totalLength=0;
409 while ((b=in.read())>=0)
410 {
411 switch ((char) b)
412 {
413 case '&':
414 value = buffer.length()==0?"":buffer.toString();
415 buffer.reset();
416 if (key != null)
417 {
418 map.add(key,value);
419 }
420 else if (value!=null&&value.length()>0)
421 {
422 map.add(value,"");
423 }
424 key = null;
425 value=null;
426 break;
427
428 case '=':
429 if (key!=null)
430 {
431 buffer.append((byte)b);
432 break;
433 }
434 key = buffer.toString();
435 buffer.reset();
436 break;
437
438 case '+':
439 buffer.append((byte)' ');
440 break;
441
442 case '%':
443 int dh=in.read();
444 int dl=in.read();
445 if (dh<0||dl<0)
446 break;
447 buffer.append((byte)((TypeUtil.convertHexDigit((byte)dh)<<4) + TypeUtil.convertHexDigit((byte)dl)));
448 break;
449 default:
450 buffer.append((byte)b);
451 break;
452 }
453 if (maxLength>=0 && (++totalLength > maxLength))
454 throw new IllegalStateException("Form too large");
455 }
456
457 if (key != null)
458 {
459 value = buffer.length()==0?"":buffer.toString();
460 buffer.reset();
461 map.add(key,value);
462 }
463 else if (buffer.length()>0)
464 {
465 map.add(buffer.toString(), "");
466 }
467 }
468 }
469
470
471 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException
472 {
473 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
474 StringBuffer buf = new StringBuffer();
475
476 int c;
477 int length=0;
478 if (maxLength<0)
479 maxLength=Integer.MAX_VALUE;
480 while ((c=input.read())>0 && length++<maxLength)
481 buf.append((char)c);
482 decodeTo(buf.toString(),map,StringUtil.__UTF8);
483 }
484
485
486
487
488
489 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength)
490 throws IOException
491 {
492 if (charset==null || StringUtil.__ISO_8859_1.equals(charset))
493 {
494 decode88591To(in,map,maxLength);
495 return;
496 }
497
498 if (StringUtil.__UTF8.equalsIgnoreCase(charset))
499 {
500 decodeUtf8To(in,map,maxLength);
501 return;
502 }
503
504 if (StringUtil.__UTF16.equalsIgnoreCase(charset))
505 {
506 decodeUtf16To(in,map,maxLength);
507 return;
508 }
509
510
511 synchronized(map)
512 {
513 String key = null;
514 String value = null;
515
516 int c;
517 int digit=0;
518 int digits=0;
519
520 int totalLength = 0;
521 ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
522
523 int size=0;
524
525 while ((c=in.read())>0)
526 {
527 switch ((char) c)
528 {
529 case '&':
530 size=output.size();
531 value = size==0?"":output.toString(charset);
532 output.setCount(0);
533 if (key != null)
534 {
535 map.add(key,value);
536 }
537 else if (value!=null&&value.length()>0)
538 {
539 map.add(value,"");
540 }
541 key = null;
542 value=null;
543 break;
544 case '=':
545 if (key!=null)
546 {
547 output.write(c);
548 break;
549 }
550 size=output.size();
551 key = size==0?"":output.toString(charset);
552 output.setCount(0);
553 break;
554 case '+':
555 output.write(' ');
556 break;
557 case '%':
558 digits=2;
559 break;
560 default:
561 if (digits==2)
562 {
563 digit=TypeUtil.convertHexDigit((byte)c);
564 digits=1;
565 }
566 else if (digits==1)
567 {
568 output.write((digit<<4) + TypeUtil.convertHexDigit((byte)c));
569 digits=0;
570 }
571 else
572 output.write(c);
573 break;
574 }
575
576 totalLength++;
577 if (maxLength>=0 && totalLength > maxLength)
578 throw new IllegalStateException("Form too large");
579 }
580
581 size=output.size();
582 if (key != null)
583 {
584 value = size==0?"":output.toString(charset);
585 output.setCount(0);
586 map.add(key,value);
587 }
588 else if (size>0)
589 map.add(output.toString(charset),"");
590 }
591 }
592
593
594
595
596
597
598 public static String decodeString(String encoded,int offset,int length,String charset)
599 {
600 if (charset==null)
601 charset=StringUtil.__UTF8;
602 byte[] bytes=null;
603 int n=0;
604
605 for (int i=0;i<length;i++)
606 {
607 char c = encoded.charAt(offset+i);
608 if (c<0||c>0xff)
609 throw new IllegalArgumentException("Not encoded");
610
611 if (c=='+')
612 {
613 if (bytes==null)
614 {
615 bytes=new byte[length*2];
616 encoded.getBytes(offset, offset+i, bytes, 0);
617 n=i;
618 }
619 bytes[n++] = (byte) ' ';
620 }
621 else if (c=='%' && (i+2)<length)
622 {
623 byte b;
624 char cn = encoded.charAt(offset+i+1);
625 if (cn>='a' && cn<='z')
626 b=(byte)(10+cn-'a');
627 else if (cn>='A' && cn<='Z')
628 b=(byte)(10+cn-'A');
629 else
630 b=(byte)(cn-'0');
631 cn = encoded.charAt(offset+i+2);
632 if (cn>='a' && cn<='z')
633 b=(byte)(b*16+10+cn-'a');
634 else if (cn>='A' && cn<='Z')
635 b=(byte)(b*16+10+cn-'A');
636 else
637 b=(byte)(b*16+cn-'0');
638
639 if (bytes==null)
640 {
641 bytes=new byte[length];
642 encoded.getBytes(offset, offset+i, bytes, 0);
643 n=i;
644 }
645 i+=2;
646 bytes[n++]=b;
647 }
648 else if (n>0)
649 bytes[n++] = (byte) c;
650 }
651
652 if (bytes==null)
653 {
654 if (offset==0 && encoded.length()==length)
655 return encoded;
656 return encoded.substring(offset,offset+length);
657 }
658
659 try
660 {
661 return new String(bytes,0,n,charset);
662 }
663 catch (UnsupportedEncodingException e)
664 {
665 Log.warn(e.toString());
666 Log.debug(e);
667 return new String(bytes,0,n);
668 }
669
670 }
671
672
673
674
675
676
677
678 public static String encodeString(String string)
679 {
680 return encodeString(string,StringUtil.__UTF8);
681 }
682
683
684
685
686
687
688 public static String encodeString(String string,String charset)
689 {
690 if (charset==null)
691 charset=StringUtil.__UTF8;
692 byte[] bytes=null;
693 try
694 {
695 bytes=string.getBytes(charset);
696 }
697 catch(UnsupportedEncodingException e)
698 {
699
700 bytes=string.getBytes();
701 }
702
703 int len=bytes.length;
704 byte[] encoded= new byte[bytes.length*3];
705 int n=0;
706 boolean noEncode=true;
707
708 for (int i=0;i<len;i++)
709 {
710 byte b = bytes[i];
711
712 if (b==' ')
713 {
714 noEncode=false;
715 encoded[n++]=(byte)'+';
716 }
717 else if (b>='a' && b<='z' ||
718 b>='A' && b<='Z' ||
719 b>='0' && b<='9')
720 {
721 encoded[n++]=b;
722 }
723 else
724 {
725 noEncode=false;
726 encoded[n++]=(byte)'%';
727 byte nibble= (byte) ((b&0xf0)>>4);
728 if (nibble>=10)
729 encoded[n++]=(byte)('A'+nibble-10);
730 else
731 encoded[n++]=(byte)('0'+nibble);
732 nibble= (byte) (b&0xf);
733 if (nibble>=10)
734 encoded[n++]=(byte)('A'+nibble-10);
735 else
736 encoded[n++]=(byte)('0'+nibble);
737 }
738 }
739
740 if (noEncode)
741 return string;
742
743 try
744 {
745 return new String(encoded,0,n,charset);
746 }
747 catch(UnsupportedEncodingException e)
748 {
749
750 return new String(encoded,0,n);
751 }
752 }
753
754
755
756
757
758 public Object clone()
759 {
760 return new UrlEncoded(this);
761 }
762 }