Mercurial Hosting > luan
diff src/org/eclipse/jetty/util/UrlEncoded.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children | 8e9db0bbf4f9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/util/UrlEncoded.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1034 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import static org.eclipse.jetty.util.TypeUtil.convertHexDigit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** Handles coding of MIME "x-www-form-urlencoded". + * <p> + * This class handles the encoding and decoding for either + * the query string of a URL or the _content of a POST HTTP request. + * + * <h4>Notes</h4> + * The UTF-8 charset is assumed, unless otherwise defined by either + * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset" + * System property. + * <p> + * The hashtable either contains String single values, vectors + * of String or arrays of Strings. + * <p> + * This class is only partially synchronised. In particular, simple + * get operations are not protected from concurrent updates. + * + * @see java.net.URLEncoder + */ +public class UrlEncoded extends MultiMap implements Cloneable +{ + private static final Logger LOG = Log.getLogger(UrlEncoded.class); + + public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8); + + /* ----------------------------------------------------------------- */ + public UrlEncoded(UrlEncoded url) + { + super(url); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded() + { + super(6); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s) + { + super(6); + decode(s,ENCODING); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(String s, String charset) + { + super(6); + decode(s,charset); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query) + { + decodeTo(query,this,ENCODING,-1); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query,String charset) + { + decodeTo(query,this,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode() + { + return encode(ENCODING,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode(String charset) + { + return encode(charset,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public synchronized String encode(String charset, boolean equalsForNullValue) + { + return encode(this,charset,equalsForNullValue); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + * @param equalsForNullValue if True, then an '=' is always used, even + * for parameters without a value. e.g. "blah?a=&b=&c=". + */ + public static String encode(MultiMap map, String charset, boolean equalsForNullValue) + { + if (charset==null) + charset=ENCODING; + + StringBuilder result = new StringBuilder(128); + + Iterator iter = map.entrySet().iterator(); + while(iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + + String key = entry.getKey().toString(); + Object list = entry.getValue(); + int s=LazyList.size(list); + + if (s==0) + { + result.append(encodeString(key,charset)); + if(equalsForNullValue) + result.append('='); + } + else + { + for (int i=0;i<s;i++) + { + if (i>0) + result.append('&'); + Object val=LazyList.get(list,i); + result.append(encodeString(key,charset)); + + if (val!=null) + { + String str=val.toString(); + if (str.length()>0) + { + result.append('='); + result.append(encodeString(str,charset)); + } + else if (equalsForNullValue) + result.append('='); + } + else if (equalsForNullValue) + result.append('='); + } + } + if (iter.hasNext()) + result.append('&'); + } + return result.toString(); + } + + + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset) + { + decodeTo(content,map,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, String charset, int maxKeys) + { + if (charset==null) + charset=ENCODING; + + synchronized(map) + { + String key = null; + String value = null; + int mark=-1; + boolean encoded=false; + for (int i=0;i<content.length();i++) + { + char c = content.charAt(i); + switch (c) + { + case '&': + int l=i-mark-1; + value = l==0?"": + (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i)); + mark=i; + encoded=false; + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + case '=': + if (key!=null) + break; + key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i); + mark=i; + encoded=false; + break; + case '+': + encoded=true; + break; + case '%': + encoded=true; + break; + } + } + + if (key != null) + { + int l=content.length()-mark-1; + value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1)); + map.add(key,value); + } + else if (mark<content.length()) + { + key = encoded + ?decodeString(content,mark+1,content.length()-mark-1,charset) + :content.substring(mark+1); + if (key != null && key.length() > 0) + { + map.add(key,""); + } + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param raw the byte[] containing the encoded parameters + * @param offset the offset within raw to decode from + * @param length the length of the section to decode + * @param map the {@link MultiMap} to populate + */ + public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map) + { + decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder()); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param raw the byte[] containing the encoded parameters + * @param offset the offset within raw to decode from + * @param length the length of the section to decode + * @param map the {@link MultiMap} to populate + * @param buffer the buffer to decode into + */ + public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer) + { + synchronized(map) + { + String key = null; + String value = null; + + // TODO cache of parameter names ??? + int end=offset+length; + for (int i=offset;i<end;i++) + { + byte b=raw[i]; + try + { + switch ((char)(0xff&b)) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append(b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + if (i+2<end) + { + if ('u'==raw[i+1]) + { + i++; + if (i+4<end) + buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i]))); + else + { + buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); + i=end; + } + } + else + buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i]))); + } + else + { + buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); + i=end; + } + break; + + default: + buffer.append(b); + break; + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toReplacedString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toReplacedString(),""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum number of keys to read or -1 for no limit + */ + public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys) + throws IOException + { + synchronized(map) + { + StringBuffer buffer = new StringBuffer(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + + case '=': + if (key!=null) + { + buffer.append((char)b); + break; + } + key = buffer.toString(); + buffer.setLength(0); + break; + + case '+': + buffer.append(' '); + break; + + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); + } + } + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); + } + break; + + default: + buffer.append((char)b); + break; + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.setLength(0); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in InputSteam to read + * @param map MultiMap to add parameters to + * @param maxLength maximum number of keys to read or -1 for no limit + */ + public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) + throws IOException + { + synchronized(map) + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + String key = null; + String value = null; + + int b; + + // TODO cache of parameter names ??? + int totalLength=0; + while ((b=in.read())>=0) + { + try + { + switch ((char) b) + { + case '&': + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + + case '=': + if (key!=null) + { + buffer.append((byte)b); + break; + } + key = buffer.toString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))); + } + } + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); + } + break; + + default: + buffer.append((byte)b); + break; + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.length()==0?"":buffer.toString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException + { + InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); + StringWriter buf = new StringWriter(8192); + IO.copy(input,buf,maxLength); + + decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in the stream containing the encoded parameters + */ + public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys) + throws IOException + { + //no charset present, use the configured default + if (charset==null) + { + charset=ENCODING; + } + + if (StringUtil.__UTF8.equalsIgnoreCase(charset)) + { + decodeUtf8To(in,map,maxLength,maxKeys); + return; + } + + if (StringUtil.__ISO_8859_1.equals(charset)) + { + decode88591To(in,map,maxLength,maxKeys); + return; + } + + if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings + { + decodeUtf16To(in,map,maxLength,maxKeys); + return; + } + + + synchronized(map) + { + String key = null; + String value = null; + + int c; + + int totalLength = 0; + ByteArrayOutputStream2 output = new ByteArrayOutputStream2(); + + int size=0; + + while ((c=in.read())>0) + { + switch ((char) c) + { + case '&': + size=output.size(); + value = size==0?"":output.toString(charset); + output.setCount(0); + if (key != null) + { + map.add(key,value); + } + else if (value!=null&&value.length()>0) + { + map.add(value,""); + } + key = null; + value=null; + if (maxKeys>0 && map.size()>maxKeys) + throw new IllegalStateException("Form too many keys"); + break; + case '=': + if (key!=null) + { + output.write(c); + break; + } + size=output.size(); + key = size==0?"":output.toString(charset); + output.setCount(0); + break; + case '+': + output.write(' '); + break; + case '%': + int code0=in.read(); + if ('u'==code0) + { + int code1=in.read(); + if (code1>=0) + { + int code2=in.read(); + if (code2>=0) + { + int code3=in.read(); + if (code3>=0) + output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset)); + } + } + + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1)); + } + break; + default: + output.write(c); + break; + } + + totalLength++; + if (maxLength>=0 && totalLength > maxLength) + throw new IllegalStateException("Form too large"); + } + + size=output.size(); + if (key != null) + { + value = size==0?"":output.toString(charset); + output.setCount(0); + map.add(key,value); + } + else if (size>0) + map.add(output.toString(charset),""); + } + } + + /* -------------------------------------------------------------- */ + /** Decode String with % encoding. + * This method makes the assumption that the majority of calls + * will need no decoding. + */ + public static String decodeString(String encoded,int offset,int length,String charset) + { + if (charset==null || StringUtil.isUTF8(charset)) + { + Utf8StringBuffer buffer=null; + + for (int i=0;i<length;i++) + { + char c = encoded.charAt(offset+i); + if (c<0||c>0xff) + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i+1); + } + else + buffer.getStringBuffer().append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i); + } + + buffer.getStringBuffer().append(' '); + } + else if (c=='%') + { + if (buffer==null) + { + buffer=new Utf8StringBuffer(length); + buffer.getStringBuffer().append(encoded,offset,offset+i); + } + + if ((i+2)<length) + { + try + { + if ('u'==encoded.charAt(offset+i+1)) + { + if((i+5)<length) + { + int o=offset+i+2; + i+=5; + String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); + buffer.getStringBuffer().append(unicode); + } + else + { + i=length; + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + } + } + else + { + int o=offset+i+1; + i+=2; + byte b=(byte)TypeUtil.parseInt(encoded,o,2,16); + buffer.append(b); + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + catch(NumberFormatException nfe) + { + LOG.debug(nfe); + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + } + } + else + { + buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); + i=length; + } + } + else if (buffer!=null) + buffer.getStringBuffer().append(c); + } + + if (buffer==null) + { + if (offset==0 && encoded.length()==length) + return encoded; + return encoded.substring(offset,offset+length); + } + + return buffer.toReplacedString(); + } + else + { + StringBuffer buffer=null; + + try + { + for (int i=0;i<length;i++) + { + char c = encoded.charAt(offset+i); + if (c<0||c>0xff) + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i+1); + } + else + buffer.append(c); + } + else if (c=='+') + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i); + } + + buffer.append(' '); + } + else if (c=='%') + { + if (buffer==null) + { + buffer=new StringBuffer(length); + buffer.append(encoded,offset,offset+i); + } + + byte[] ba=new byte[length]; + int n=0; + while(c>=0 && c<=0xff) + { + if (c=='%') + { + if(i+2<length) + { + try + { + if ('u'==encoded.charAt(offset+i+1)) + { + if (i+6<length) + { + int o=offset+i+2; + i+=6; + String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16))); + byte[] reencoded = unicode.getBytes(charset); + System.arraycopy(reencoded,0,ba,n,reencoded.length); + n+=reencoded.length; + } + else + { + ba[n++] = (byte)'?'; + i=length; + } + } + else + { + int o=offset+i+1; + i+=3; + ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16); + n++; + } + } + catch(NumberFormatException nfe) + { + LOG.ignore(nfe); + ba[n++] = (byte)'?'; + } + } + else + { + ba[n++] = (byte)'?'; + i=length; + } + } + else if (c=='+') + { + ba[n++]=(byte)' '; + i++; + } + else + { + ba[n++]=(byte)c; + i++; + } + + if (i>=length) + break; + c = encoded.charAt(offset+i); + } + + i--; + buffer.append(new String(ba,0,n,charset)); + + } + else if (buffer!=null) + buffer.append(c); + } + + if (buffer==null) + { + if (offset==0 && encoded.length()==length) + return encoded; + return encoded.substring(offset,offset+length); + } + + return buffer.toString(); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * @param string + * @return encoded string. + */ + public static String encodeString(String string) + { + return encodeString(string,ENCODING); + } + + /* ------------------------------------------------------------ */ + /** Perform URL encoding. + * @param string + * @return encoded string. + */ + public static String encodeString(String string,String charset) + { + if (charset==null) + charset=ENCODING; + byte[] bytes=null; + try + { + bytes=string.getBytes(charset); + } + catch(UnsupportedEncodingException e) + { + // LOG.warn(LogSupport.EXCEPTION,e); + bytes=string.getBytes(); + } + + int len=bytes.length; + byte[] encoded= new byte[bytes.length*3]; + int n=0; + boolean noEncode=true; + + for (int i=0;i<len;i++) + { + byte b = bytes[i]; + + if (b==' ') + { + noEncode=false; + encoded[n++]=(byte)'+'; + } + else if (b>='a' && b<='z' || + b>='A' && b<='Z' || + b>='0' && b<='9') + { + encoded[n++]=b; + } + else + { + noEncode=false; + encoded[n++]=(byte)'%'; + byte nibble= (byte) ((b&0xf0)>>4); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + nibble= (byte) (b&0xf); + if (nibble>=10) + encoded[n++]=(byte)('A'+nibble-10); + else + encoded[n++]=(byte)('0'+nibble); + } + } + + if (noEncode) + return string; + + try + { + return new String(encoded,0,n,charset); + } + catch(UnsupportedEncodingException e) + { + // LOG.warn(LogSupport.EXCEPTION,e); + return new String(encoded,0,n); + } + } + + + /* ------------------------------------------------------------ */ + /** + */ + @Override + public Object clone() + { + return new UrlEncoded(this); + } +}