X-Git-Url: https://code.wpia.club/?p=gigi.git;a=blobdiff_plain;f=lib%2Fjetty%2Forg%2Feclipse%2Fjetty%2Futil%2FUrlEncoded.java;fp=lib%2Fjetty%2Forg%2Feclipse%2Fjetty%2Futil%2FUrlEncoded.java;h=ef7f2f5fa9ef88ca04a24bcd720713ad34756067;hp=0000000000000000000000000000000000000000;hb=73ef54a38e3930a1a789cdc6b5fa23cdd4c9d086;hpb=515007c7c1351045420669d65b59c08fa46850f2 diff --git a/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java b/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java new file mode 100644 index 00000000..ef7f2f5f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java @@ -0,0 +1,1064 @@ +// +// ======================================================================== +// 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.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +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". + *

+ * This class handles the encoding and decoding for either + * the query string of a URL or the _content of a POST HTTP request. + * + *

Notes

+ * 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. + *

+ * The hashtable either contains String single values, vectors + * of String or arrays of Strings. + *

+ * This class is only partially synchronised. In particular, simple + * get operations are not protected from concurrent updates. + * + * @see java.net.URLEncoder + */ +@SuppressWarnings("serial") +public class UrlEncoded extends MultiMap implements Cloneable +{ + static final Logger LOG = Log.getLogger(UrlEncoded.class); + + public static final Charset ENCODING; + static + { + Charset encoding; + try + { + String charset = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset"); + encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset); + } + catch(Exception e) + { + LOG.warn(e); + encoding=StandardCharsets.UTF_8; + } + ENCODING=encoding; + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded(UrlEncoded url) + { + super(url); + } + + /* ----------------------------------------------------------------- */ + public UrlEncoded() + { + } + + public UrlEncoded(String query) + { + decodeTo(query,this,ENCODING,-1); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query) + { + decodeTo(query,this,ENCODING,-1); + } + + /* ----------------------------------------------------------------- */ + public void decode(String query,Charset charset) + { + decodeTo(query,this,charset,-1); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode() + { + return encode(ENCODING,false); + } + + /* -------------------------------------------------------------- */ + /** Encode Hashtable with % encoding. + */ + public String encode(Charset 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(Charset 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, Charset charset, boolean equalsForNullValue) + { + if (charset==null) + charset=ENCODING; + + StringBuilder result = new StringBuilder(128); + + boolean delim = false; + for(Map.Entry> entry: map.entrySet()) + { + String key = entry.getKey().toString(); + List list = entry.getValue(); + int s=list.size(); + + if (delim) + { + result.append('&'); + } + + if (s==0) + { + result.append(encodeString(key,charset)); + if(equalsForNullValue) + result.append('='); + } + else + { + for (int i=0;i0) + result.append('&'); + String val=list.get(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('='); + } + } + delim = true; + } + 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, int maxKeys) + { + decodeTo(content,map,charset==null?null:Charset.forName(charset),maxKeys); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param content the string containing the encoded parameters + */ + public static void decodeTo(String content, MultiMap map, Charset 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;i0) + { + 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 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) + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + synchronized(map) + { + String key = null; + String value = null; + + int end=offset+length; + for (int i=offset;i0) + { + map.add(value,""); + } + key = null; + value=null; + break; + + case '=': + if (key!=null) + { + buffer.append(b); + break; + } + key = buffer.toReplacedString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + if (i+20) + { + 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; + + 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; + + int totalLength=0; + while ((b=in.read())>=0) + { + try + { + switch ((char) b) + { + case '&': + value = buffer.toReplacedString(); + 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.toReplacedString(); + buffer.reset(); + break; + + case '+': + buffer.append((byte)' '); + break; + + case '%': + int code0=in.read(); + boolean decoded=false; + if ('u'==code0) + { + code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u' + if (code0>=0) + { + 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))); + decoded=true; + } + } + } + } + } + else if (code0>=0) + { + int code1=in.read(); + if (code1>=0) + { + buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1))); + decoded=true; + } + } + + if (!decoded) + buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); + + break; + + default: + buffer.append((byte)b); + break; + } + } + catch(NotUtf8Exception e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + catch(NumberFormatException e) + { + buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3); + LOG.warn(e.toString()); + LOG.debug(e); + } + if (maxLength>=0 && (++totalLength > maxLength)) + throw new IllegalStateException("Form too large"); + } + + if (key != null) + { + value = buffer.toReplacedString(); + buffer.reset(); + map.add(key,value); + } + else if (buffer.length()>0) + { + map.add(buffer.toReplacedString(), ""); + } + } + } + + /* -------------------------------------------------------------- */ + public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException + { + InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16); + StringWriter buf = new StringWriter(8192); + IO.copy(input,buf,maxLength); + + decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,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 + { + if (charset==null) + { + if (ENCODING.equals(StandardCharsets.UTF_8)) + decodeUtf8To(in,map,maxLength,maxKeys); + else + decodeTo(in,map,ENCODING,maxLength,maxKeys); + } + else if (StringUtil.__UTF8.equalsIgnoreCase(charset)) + decodeUtf8To(in,map,maxLength,maxKeys); + else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset)) + decode88591To(in,map,maxLength,maxKeys); + else if (StringUtil.__UTF16.equalsIgnoreCase(charset)) + decodeUtf16To(in,map,maxLength,maxKeys); + else + decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys); + } + + /* -------------------------------------------------------------- */ + /** Decoded parameters to Map. + * @param in the stream containing the encoded parameters + */ + public static void decodeTo(InputStream in, MultiMap map, Charset charset, int maxLength, int maxKeys) + throws IOException + { + //no charset present, use the configured default + if (charset==null) + charset=ENCODING; + + if (StandardCharsets.UTF_8.equals(charset)) + { + decodeUtf8To(in,map,maxLength,maxKeys); + return; + } + + if (StandardCharsets.ISO_8859_1.equals(charset)) + { + decode88591To(in,map,maxLength,maxKeys); + return; + } + + if (StandardCharsets.UTF_16.equals(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,Charset charset) + { + if (charset==null || StandardCharsets.UTF_8.equals(charset)) + { + Utf8StringBuffer buffer=null; + + for (int i=0;i0xff) + { + 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)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) + 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(); + } + + } + + /* ------------------------------------------------------------ */ + /** 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,Charset charset) + { + if (charset==null) + charset=ENCODING; + byte[] bytes=null; + bytes=string.getBytes(charset); + + int len=bytes.length; + byte[] encoded= new byte[bytes.length*3]; + int n=0; + boolean noEncode=true; + + for (int i=0;i='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; + + return new String(encoded,0,n,charset); + } + + + /* ------------------------------------------------------------ */ + /** + */ + @Override + public Object clone() + { + return new UrlEncoded(this); + } +}