View Javadoc

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