Home | History | Annotate | Download | only in util
      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