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