view src/org/eclipse/jetty/util/UrlEncoded.java @ 1075:ebb0f1343ef6

remove JBuffer
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 10 Nov 2016 03:08:20 -0700
parents 9d357b9e4bcb
children
line wrap: on
line source

//
//  ========================================================================
//  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.slf4j.Logger;
import org.slf4j.LoggerFactory;

/* ------------------------------------------------------------ */
/** 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 = LoggerFactory.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.trace("",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);
	}
}