1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.util; 20 21 import static org.eclipse.jetty.util.TypeUtil.convertHexDigit; 22 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.InputStreamReader; 26 import java.io.StringWriter; 27 import java.io.UnsupportedEncodingException; 28 import java.util.Iterator; 29 import java.util.Map; 30 31 import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; 32 import org.eclipse.jetty.util.log.Log; 33 import org.eclipse.jetty.util.log.Logger; 34 35 /* ------------------------------------------------------------ */ 36 /** Handles coding of MIME "x-www-form-urlencoded". 37 * <p> 38 * This class handles the encoding and decoding for either 39 * the query string of a URL or the _content of a POST HTTP request. 40 * 41 * <h4>Notes</h4> 42 * The UTF-8 charset is assumed, unless otherwise defined by either 43 * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset" 44 * System property. 45 * <p> 46 * The hashtable either contains String single values, vectors 47 * of String or arrays of Strings. 48 * <p> 49 * This class is only partially synchronised. In particular, simple 50 * get operations are not protected from concurrent updates. 51 * 52 * @see java.net.URLEncoder 53 */ 54 public class UrlEncoded extends MultiMap implements Cloneable 55 { 56 private static final Logger LOG = Log.getLogger(UrlEncoded.class); 57 58 public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8); 59 60 /* ----------------------------------------------------------------- */ 61 public UrlEncoded(UrlEncoded url) 62 { 63 super(url); 64 } 65 66 /* ----------------------------------------------------------------- */ 67 public UrlEncoded() 68 { 69 super(6); 70 } 71 72 /* ----------------------------------------------------------------- */ 73 public UrlEncoded(String s) 74 { 75 super(6); 76 decode(s,ENCODING); 77 } 78 79 /* ----------------------------------------------------------------- */ 80 public UrlEncoded(String s, String charset) 81 { 82 super(6); 83 decode(s,charset); 84 } 85 86 /* ----------------------------------------------------------------- */ 87 public void decode(String query) 88 { 89 decodeTo(query,this,ENCODING,-1); 90 } 91 92 /* ----------------------------------------------------------------- */ 93 public void decode(String query,String charset) 94 { 95 decodeTo(query,this,charset,-1); 96 } 97 98 /* -------------------------------------------------------------- */ 99 /** Encode Hashtable with % encoding. 100 */ 101 public String encode() 102 { 103 return encode(ENCODING,false); 104 } 105 106 /* -------------------------------------------------------------- */ 107 /** Encode Hashtable with % encoding. 108 */ 109 public String encode(String charset) 110 { 111 return encode(charset,false); 112 } 113 114 /* -------------------------------------------------------------- */ 115 /** Encode Hashtable with % encoding. 116 * @param equalsForNullValue if True, then an '=' is always used, even 117 * for parameters without a value. e.g. "blah?a=&b=&c=". 118 */ 119 public synchronized String encode(String charset, boolean equalsForNullValue) 120 { 121 return encode(this,charset,equalsForNullValue); 122 } 123 124 /* -------------------------------------------------------------- */ 125 /** Encode Hashtable with % encoding. 126 * @param equalsForNullValue if True, then an '=' is always used, even 127 * for parameters without a value. e.g. "blah?a=&b=&c=". 128 */ 129 public static String encode(MultiMap map, String charset, boolean equalsForNullValue) 130 { 131 if (charset==null) 132 charset=ENCODING; 133 134 StringBuilder result = new StringBuilder(128); 135 136 Iterator iter = map.entrySet().iterator(); 137 while(iter.hasNext()) 138 { 139 Map.Entry entry = (Map.Entry)iter.next(); 140 141 String key = entry.getKey().toString(); 142 Object list = entry.getValue(); 143 int s=LazyList.size(list); 144 145 if (s==0) 146 { 147 result.append(encodeString(key,charset)); 148 if(equalsForNullValue) 149 result.append('='); 150 } 151 else 152 { 153 for (int i=0;i<s;i++) 154 { 155 if (i>0) 156 result.append('&'); 157 Object val=LazyList.get(list,i); 158 result.append(encodeString(key,charset)); 159 160 if (val!=null) 161 { 162 String str=val.toString(); 163 if (str.length()>0) 164 { 165 result.append('='); 166 result.append(encodeString(str,charset)); 167 } 168 else if (equalsForNullValue) 169 result.append('='); 170 } 171 else if (equalsForNullValue) 172 result.append('='); 173 } 174 } 175 if (iter.hasNext()) 176 result.append('&'); 177 } 178 return result.toString(); 179 } 180 181 182 183 /* -------------------------------------------------------------- */ 184 /** Decoded parameters to Map. 185 * @param content the string containing the encoded parameters 186 */ 187 public static void decodeTo(String content, MultiMap map, String charset) 188 { 189 decodeTo(content,map,charset,-1); 190 } 191 192 /* -------------------------------------------------------------- */ 193 /** Decoded parameters to Map. 194 * @param content the string containing the encoded parameters 195 */ 196 public static void decodeTo(String content, MultiMap map, String charset, int maxKeys) 197 { 198 if (charset==null) 199 charset=ENCODING; 200 201 synchronized(map) 202 { 203 String key = null; 204 String value = null; 205 int mark=-1; 206 boolean encoded=false; 207 for (int i=0;i<content.length();i++) 208 { 209 char c = content.charAt(i); 210 switch (c) 211 { 212 case '&': 213 int l=i-mark-1; 214 value = l==0?"": 215 (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i)); 216 mark=i; 217 encoded=false; 218 if (key != null) 219 { 220 map.add(key,value); 221 } 222 else if (value!=null&&value.length()>0) 223 { 224 map.add(value,""); 225 } 226 key = null; 227 value=null; 228 if (maxKeys>0 && map.size()>maxKeys) 229 throw new IllegalStateException("Form too many keys"); 230 break; 231 case '=': 232 if (key!=null) 233 break; 234 key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i); 235 mark=i; 236 encoded=false; 237 break; 238 case '+': 239 encoded=true; 240 break; 241 case '%': 242 encoded=true; 243 break; 244 } 245 } 246 247 if (key != null) 248 { 249 int l=content.length()-mark-1; 250 value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1)); 251 map.add(key,value); 252 } 253 else if (mark<content.length()) 254 { 255 key = encoded 256 ?decodeString(content,mark+1,content.length()-mark-1,charset) 257 :content.substring(mark+1); 258 if (key != null && key.length() > 0) 259 { 260 map.add(key,""); 261 } 262 } 263 } 264 } 265 266 /* -------------------------------------------------------------- */ 267 /** Decoded parameters to Map. 268 * @param raw the byte[] containing the encoded parameters 269 * @param offset the offset within raw to decode from 270 * @param length the length of the section to decode 271 * @param map the {@link MultiMap} to populate 272 */ 273 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map) 274 { 275 decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder()); 276 } 277 278 /* -------------------------------------------------------------- */ 279 /** Decoded parameters to Map. 280 * @param raw the byte[] containing the encoded parameters 281 * @param offset the offset within raw to decode from 282 * @param length the length of the section to decode 283 * @param map the {@link MultiMap} to populate 284 * @param buffer the buffer to decode into 285 */ 286 public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer) 287 { 288 synchronized(map) 289 { 290 String key = null; 291 String value = null; 292 293 // TODO cache of parameter names ??? 294 int end=offset+length; 295 for (int i=offset;i<end;i++) 296 { 297 byte b=raw[i]; 298 try 299 { 300 switch ((char)(0xff&b)) 301 { 302 case '&': 303 value = buffer.length()==0?"":buffer.toString(); 304 buffer.reset(); 305 if (key != null) 306 { 307 map.add(key,value); 308 } 309 else if (value!=null&&value.length()>0) 310 { 311 map.add(value,""); 312 } 313 key = null; 314 value=null; 315 break; 316 317 case '=': 318 if (key!=null) 319 { 320 buffer.append(b); 321 break; 322 } 323 key = buffer.toString(); 324 buffer.reset(); 325 break; 326 327 case '+': 328 buffer.append((byte)' '); 329 break; 330 331 case '%': 332 if (i+2<end) 333 { 334 if ('u'==raw[i+1]) 335 { 336 i++; 337 if (i+4<end) 338 buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i]))); 339 else 340 { 341 buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); 342 i=end; 343 } 344 } 345 else 346 buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i]))); 347 } 348 else 349 { 350 buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); 351 i=end; 352 } 353 break; 354 355 default: 356 buffer.append(b); 357 break; 358 } 359 } 360 catch(NotUtf8Exception e) 361 { 362 LOG.warn(e.toString()); 363 LOG.debug(e); 364 } 365 } 366 367 if (key != null) 368 { 369 value = buffer.length()==0?"":buffer.toReplacedString(); 370 buffer.reset(); 371 map.add(key,value); 372 } 373 else if (buffer.length()>0) 374 { 375 map.add(buffer.toReplacedString(),""); 376 } 377 } 378 } 379 380 /* -------------------------------------------------------------- */ 381 /** Decoded parameters to Map. 382 * @param in InputSteam to read 383 * @param map MultiMap to add parameters to 384 * @param maxLength maximum number of keys to read or -1 for no limit 385 */ 386 public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys) 387 throws IOException 388 { 389 synchronized(map) 390 { 391 StringBuffer buffer = new StringBuffer(); 392 String key = null; 393 String value = null; 394 395 int b; 396 397 // TODO cache of parameter names ??? 398 int totalLength=0; 399 while ((b=in.read())>=0) 400 { 401 switch ((char) b) 402 { 403 case '&': 404 value = buffer.length()==0?"":buffer.toString(); 405 buffer.setLength(0); 406 if (key != null) 407 { 408 map.add(key,value); 409 } 410 else if (value!=null&&value.length()>0) 411 { 412 map.add(value,""); 413 } 414 key = null; 415 value=null; 416 if (maxKeys>0 && map.size()>maxKeys) 417 throw new IllegalStateException("Form too many keys"); 418 break; 419 420 case '=': 421 if (key!=null) 422 { 423 buffer.append((char)b); 424 break; 425 } 426 key = buffer.toString(); 427 buffer.setLength(0); 428 break; 429 430 case '+': 431 buffer.append(' '); 432 break; 433 434 case '%': 435 int code0=in.read(); 436 if ('u'==code0) 437 { 438 int code1=in.read(); 439 if (code1>=0) 440 { 441 int code2=in.read(); 442 if (code2>=0) 443 { 444 int code3=in.read(); 445 if (code3>=0) 446 buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); 447 } 448 } 449 } 450 else if (code0>=0) 451 { 452 int code1=in.read(); 453 if (code1>=0) 454 buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); 455 } 456 break; 457 458 default: 459 buffer.append((char)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.setLength(0); 470 map.add(key,value); 471 } 472 else if (buffer.length()>0) 473 { 474 map.add(buffer.toString(), ""); 475 } 476 } 477 } 478 479 /* -------------------------------------------------------------- */ 480 /** Decoded parameters to Map. 481 * @param in InputSteam to read 482 * @param map MultiMap to add parameters to 483 * @param maxLength maximum number of keys to read or -1 for no limit 484 */ 485 public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) 486 throws IOException 487 { 488 synchronized(map) 489 { 490 Utf8StringBuilder buffer = new Utf8StringBuilder(); 491 String key = null; 492 String value = null; 493 494 int b; 495 496 // TODO cache of parameter names ??? 497 int totalLength=0; 498 while ((b=in.read())>=0) 499 { 500 try 501 { 502 switch ((char) b) 503 { 504 case '&': 505 value = buffer.length()==0?"":buffer.toString(); 506 buffer.reset(); 507 if (key != null) 508 { 509 map.add(key,value); 510 } 511 else if (value!=null&&value.length()>0) 512 { 513 map.add(value,""); 514 } 515 key = null; 516 value=null; 517 if (maxKeys>0 && map.size()>maxKeys) 518 throw new IllegalStateException("Form too many keys"); 519 break; 520 521 case '=': 522 if (key!=null) 523 { 524 buffer.append((byte)b); 525 break; 526 } 527 key = buffer.toString(); 528 buffer.reset(); 529 break; 530 531 case '+': 532 buffer.append((byte)' '); 533 break; 534 535 case '%': 536 int code0=in.read(); 537 if ('u'==code0) 538 { 539 int code1=in.read(); 540 if (code1>=0) 541 { 542 int code2=in.read(); 543 if (code2>=0) 544 { 545 int code3=in.read(); 546 if (code3>=0) 547 buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); 548 } 549 } 550 } 551 else if (code0>=0) 552 { 553 int code1=in.read(); 554 if (code1>=0) 555 buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); 556 } 557 break; 558 559 default: 560 buffer.append((byte)b); 561 break; 562 } 563 } 564 catch(NotUtf8Exception e) 565 { 566 LOG.warn(e.toString()); 567 LOG.debug(e); 568 } 569 if (maxLength>=0 && (++totalLength > maxLength)) 570 throw new IllegalStateException("Form too large"); 571 } 572 573 if (key != null) 574 { 575 value = buffer.length()==0?"":buffer.toString(); 576 buffer.reset(); 577 map.add(key,value); 578 } 579 else if (buffer.length()>0) 580 { 581 map.add(buffer.toString(), ""); 582 } 583 } 584 } 585 586 /* -------------------------------------------------------------- */ 587 public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException 588 { 589 InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); 590 StringWriter buf = new StringWriter(8192); 591 IO.copy(input,buf,maxLength); 592 593 decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys); 594 } 595 596 /* -------------------------------------------------------------- */ 597 /** Decoded parameters to Map. 598 * @param in the stream containing the encoded parameters 599 */ 600 public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys) 601 throws IOException 602 { 603 //no charset present, use the configured default 604 if (charset==null) 605 { 606 charset=ENCODING; 607 } 608 609 if (StringUtil.__UTF8.equalsIgnoreCase(charset)) 610 { 611 decodeUtf8To(in,map,maxLength,maxKeys); 612 return; 613 } 614 615 if (StringUtil.__ISO_8859_1.equals(charset)) 616 { 617 decode88591To(in,map,maxLength,maxKeys); 618 return; 619 } 620 621 if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings 622 { 623 decodeUtf16To(in,map,maxLength,maxKeys); 624 return; 625 } 626 627 628 synchronized(map) 629 { 630 String key = null; 631 String value = null; 632 633 int c; 634 635 int totalLength = 0; 636 ByteArrayOutputStream2 output = new ByteArrayOutputStream2(); 637 638 int size=0; 639 640 while ((c=in.read())>0) 641 { 642 switch ((char) c) 643 { 644 case '&': 645 size=output.size(); 646 value = size==0?"":output.toString(charset); 647 output.setCount(0); 648 if (key != null) 649 { 650 map.add(key,value); 651 } 652 else if (value!=null&&value.length()>0) 653 { 654 map.add(value,""); 655 } 656 key = null; 657 value=null; 658 if (maxKeys>0 && map.size()>maxKeys) 659 throw new IllegalStateException("Form too many keys"); 660 break; 661 case '=': 662 if (key!=null) 663 { 664 output.write(c); 665 break; 666 } 667 size=output.size(); 668 key = size==0?"":output.toString(charset); 669 output.setCount(0); 670 break; 671 case '+': 672 output.write(' '); 673 break; 674 case '%': 675 int code0=in.read(); 676 if ('u'==code0) 677 { 678 int code1=in.read(); 679 if (code1>=0) 680 { 681 int code2=in.read(); 682 if (code2>=0) 683 { 684 int code3=in.read(); 685 if (code3>=0) 686 output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset)); 687 } 688 } 689 690 } 691 else if (code0>=0) 692 { 693 int code1=in.read(); 694 if (code1>=0) 695 output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1)); 696 } 697 break; 698 default: 699 output.write(c); 700 break; 701 } 702 703 totalLength++; 704 if (maxLength>=0 && totalLength > maxLength) 705 throw new IllegalStateException("Form too large"); 706 } 707 708 size=output.size(); 709 if (key != null) 710 { 711 value = size==0?"":output.toString(charset); 712 output.setCount(0); 713 map.add(key,value); 714 } 715 else if (size>0) 716 map.add(output.toString(charset),""); 717 } 718 } 719 720 /* -------------------------------------------------------------- */ 721 /** Decode String with % encoding. 722 * This method makes the assumption that the majority of calls 723 * will need no decoding. 724 */ 725 public static String decodeString(String encoded,int offset,int length,String charset) 726 { 727 if (charset==null || StringUtil.isUTF8(charset)) 728 { 729 Utf8StringBuffer buffer=null; 730 731 for (int i=0;i<length;i++) 732 { 733 char c = encoded.charAt(offset+i); 734 if (c<0||c>0xff) 735 { 736 if (buffer==null) 737 { 738 buffer=new Utf8StringBuffer(length); 739 buffer.getStringBuffer().append(encoded,offset,offset+i+1); 740 } 741 else 742 buffer.getStringBuffer().append(c); 743 } 744 else if (c=='+') 745 { 746 if (buffer==null) 747 { 748 buffer=new Utf8StringBuffer(length); 749 buffer.getStringBuffer().append(encoded,offset,offset+i); 750 } 751 752 buffer.getStringBuffer().append(' '); 753 } 754 else if (c=='%') 755 { 756 if (buffer==null) 757 { 758 buffer=new Utf8StringBuffer(length); 759 buffer.getStringBuffer().append(encoded,offset,offset+i); 760 } 761 762 if ((i+2)<length) 763 { 764 try 765 { 766 if ('u'==encoded.charAt(offset+i+1)) 767 { 768 if((i+5)<length) 769 { 770 int o=offset+i+2; 771 i+=5; 772 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); 773 buffer.getStringBuffer().append(unicode); 774 } 775 else 776 { 777 i=length; 778 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 779 } 780 } 781 else 782 { 783 int o=offset+i+1; 784 i+=2; 785 byte b=(byte)TypeUtil.parseInt(encoded,o,2,16); 786 buffer.append(b); 787 } 788 } 789 catch(NotUtf8Exception e) 790 { 791 LOG.warn(e.toString()); 792 LOG.debug(e); 793 } 794 catch(NumberFormatException nfe) 795 { 796 LOG.debug(nfe); 797 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 798 } 799 } 800 else 801 { 802 buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 803 i=length; 804 } 805 } 806 else if (buffer!=null) 807 buffer.getStringBuffer().append(c); 808 } 809 810 if (buffer==null) 811 { 812 if (offset==0 && encoded.length()==length) 813 return encoded; 814 return encoded.substring(offset,offset+length); 815 } 816 817 return buffer.toReplacedString(); 818 } 819 else 820 { 821 StringBuffer buffer=null; 822 823 try 824 { 825 for (int i=0;i<length;i++) 826 { 827 char c = encoded.charAt(offset+i); 828 if (c<0||c>0xff) 829 { 830 if (buffer==null) 831 { 832 buffer=new StringBuffer(length); 833 buffer.append(encoded,offset,offset+i+1); 834 } 835 else 836 buffer.append(c); 837 } 838 else if (c=='+') 839 { 840 if (buffer==null) 841 { 842 buffer=new StringBuffer(length); 843 buffer.append(encoded,offset,offset+i); 844 } 845 846 buffer.append(' '); 847 } 848 else if (c=='%') 849 { 850 if (buffer==null) 851 { 852 buffer=new StringBuffer(length); 853 buffer.append(encoded,offset,offset+i); 854 } 855 856 byte[] ba=new byte[length]; 857 int n=0; 858 while(c>=0 && c<=0xff) 859 { 860 if (c=='%') 861 { 862 if(i+2<length) 863 { 864 try 865 { 866 if ('u'==encoded.charAt(offset+i+1)) 867 { 868 if (i+6<length) 869 { 870 int o=offset+i+2; 871 i+=6; 872 String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); 873 byte[] reencoded = unicode.getBytes(charset); 874 System.arraycopy(reencoded,0,ba,n,reencoded.length); 875 n+=reencoded.length; 876 } 877 else 878 { 879 ba[n++] = (byte)'?'; 880 i=length; 881 } 882 } 883 else 884 { 885 int o=offset+i+1; 886 i+=3; 887 ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16); 888 n++; 889 } 890 } 891 catch(NumberFormatException nfe) 892 { 893 LOG.ignore(nfe); 894 ba[n++] = (byte)'?'; 895 } 896 } 897 else 898 { 899 ba[n++] = (byte)'?'; 900 i=length; 901 } 902 } 903 else if (c=='+') 904 { 905 ba[n++]=(byte)' '; 906 i++; 907 } 908 else 909 { 910 ba[n++]=(byte)c; 911 i++; 912 } 913 914 if (i>=length) 915 break; 916 c = encoded.charAt(offset+i); 917 } 918 919 i--; 920 buffer.append(new String(ba,0,n,charset)); 921 922 } 923 else if (buffer!=null) 924 buffer.append(c); 925 } 926 927 if (buffer==null) 928 { 929 if (offset==0 && encoded.length()==length) 930 return encoded; 931 return encoded.substring(offset,offset+length); 932 } 933 934 return buffer.toString(); 935 } 936 catch (UnsupportedEncodingException e) 937 { 938 throw new RuntimeException(e); 939 } 940 } 941 942 } 943 944 /* ------------------------------------------------------------ */ 945 /** Perform URL encoding. 946 * @param string 947 * @return encoded string. 948 */ 949 public static String encodeString(String string) 950 { 951 return encodeString(string,ENCODING); 952 } 953 954 /* ------------------------------------------------------------ */ 955 /** Perform URL encoding. 956 * @param string 957 * @return encoded string. 958 */ 959 public static String encodeString(String string,String charset) 960 { 961 if (charset==null) 962 charset=ENCODING; 963 byte[] bytes=null; 964 try 965 { 966 bytes=string.getBytes(charset); 967 } 968 catch(UnsupportedEncodingException e) 969 { 970 // LOG.warn(LogSupport.EXCEPTION,e); 971 bytes=string.getBytes(); 972 } 973 974 int len=bytes.length; 975 byte[] encoded= new byte[bytes.length*3]; 976 int n=0; 977 boolean noEncode=true; 978 979 for (int i=0;i<len;i++) 980 { 981 byte b = bytes[i]; 982 983 if (b==' ') 984 { 985 noEncode=false; 986 encoded[n++]=(byte)'+'; 987 } 988 else if (b>='a' && b<='z' || 989 b>='A' && b<='Z' || 990 b>='0' && b<='9') 991 { 992 encoded[n++]=b; 993 } 994 else 995 { 996 noEncode=false; 997 encoded[n++]=(byte)'%'; 998 byte nibble= (byte) ((b&0xf0)>>4); 999 if (nibble>=10) 1000 encoded[n++]=(byte)('A'+nibble-10); 1001 else 1002 encoded[n++]=(byte)('0'+nibble); 1003 nibble= (byte) (b&0xf); 1004 if (nibble>=10) 1005 encoded[n++]=(byte)('A'+nibble-10); 1006 else 1007 encoded[n++]=(byte)('0'+nibble); 1008 } 1009 } 1010 1011 if (noEncode) 1012 return string; 1013 1014 try 1015 { 1016 return new String(encoded,0,n,charset); 1017 } 1018 catch(UnsupportedEncodingException e) 1019 { 1020 // LOG.warn(LogSupport.EXCEPTION,e); 1021 return new String(encoded,0,n); 1022 } 1023 } 1024 1025 1026 /* ------------------------------------------------------------ */ 1027 /** 1028 */ 1029 @Override 1030 public Object clone() 1031 { 1032 return new UrlEncoded(this); 1033 } 1034 } 1035