View Javadoc

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