org.eclipse.jetty.util.UrlEncoded Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
//
// ========================================================================
// Copyright (c) 1995-2018 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.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);
}
/* ----------------------------------------------------------------- */
public void decode(String query)
{
decodeTo(query,this,ENCODING);
}
/* ----------------------------------------------------------------- */
public void decode(String query,Charset charset)
{
decodeTo(query,this,charset);
}
/* -------------------------------------------------------------- */
/** Encode MultiMap with % encoding for UTF8 sequences.
* @return the MultiMap as a string with % encoding
*/
public String encode()
{
return encode(ENCODING,false);
}
/* -------------------------------------------------------------- */
/** Encode MultiMap with % encoding for arbitrary Charset sequences.
* @param charset the charset to use for encoding
* @return the MultiMap as a string encoded with % encodings
*/
public String encode(Charset charset)
{
return encode(charset,false);
}
/* -------------------------------------------------------------- */
/**
* Encode MultiMap with % encoding.
* @param charset the charset to encode with
* @param equalsForNullValue if True, then an '=' is always used, even
* for parameters without a value. e.g. "blah?a=&b=&c="
.
* @return the MultiMap as a string encoded with % encodings
*/
public synchronized String encode(Charset charset, boolean equalsForNullValue)
{
return encode(this,charset,equalsForNullValue);
}
/* -------------------------------------------------------------- */
/** Encode MultiMap with % encoding.
* @param map the map to encode
* @param charset the charset to use for encoding (uses default encoding if null)
* @param equalsForNullValue if True, then an '=' is always used, even
* for parameters without a value. e.g. "blah?a=&b=&c="
.
* @return the MultiMap as a string encoded with % encodings.
*/
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
* @param map the MultiMap to put parsed query parameters into
* @param charset the charset to use for decoding
*/
public static void decodeTo(String content, MultiMap map, String charset)
{
decodeTo(content,map,charset==null?null:Charset.forName(charset));
}
/* -------------------------------------------------------------- */
/** Decoded parameters to Map.
* @param content the string containing the encoded parameters
* @param map the MultiMap to put parsed query parameters into
* @param charset the charset to use for decoding
*/
public static void decodeTo(String content, MultiMap map, Charset charset)
{
if (charset==null)
charset=ENCODING;
if (StandardCharsets.UTF_8.equals(charset))
{
decodeUtf8To(content,0,content.length(),map);
return;
}
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;
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,"");
}
}
}
}
/* -------------------------------------------------------------- */
public static void decodeUtf8To(String query, MultiMap map)
{
decodeUtf8To(query,0,query.length(),map);
}
/* -------------------------------------------------------------- */
/** Decoded parameters to Map.
* @param query the string 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(String query,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(c);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
if (i+20)
{
map.add(buffer.toReplacedString(),"");
}
}
}
/* -------------------------------------------------------------- */
/** Decoded parameters to MultiMap, using ISO8859-1 encodings.
*
* @param in InputSteam to read
* @param map MultiMap to add parameters to
* @param maxLength maximum length of form to read
* @param maxKeys maximum number of keys to read or -1 for no limit
* @throws IOException if unable to decode inputstream as ISO8859-1
*/
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(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
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();
int code1=in.read();
buffer.append(decodeHexChar(code0,code1));
break;
default:
buffer.append((char)b);
break;
}
if (maxLength>=0 && (++totalLength > maxLength))
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
}
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 form length to decode
* @param maxKeys the maximum number of keys to read or -1 for no limit
* @throws IOException if unable to decode input stream
*/
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)
{
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(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
break;
case '=':
if (key!=null)
{
buffer.append((byte)b);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
char code0= (char) in.read();
char code1= (char) in.read();
buffer.append(decodeHexByte(code0,code1));
break;
default:
buffer.append((byte)b);
break;
}
if (maxLength>=0 && (++totalLength > maxLength))
throw new IllegalStateException("Form is 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);
// TODO implement maxKeys
decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16);
}
/* -------------------------------------------------------------- */
/** Decoded parameters to Map.
* @param in the stream containing the encoded parameters
* @param map the MultiMap to decode into
* @param charset the charset to use for decoding
* @param maxLength the maximum length of the form to decode
* @param maxKeys the maximum number of keys to decode
* @throws IOException if unable to decode input stream
*/
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
* @param map the MultiMap to decode into
* @param charset the charset to use for decoding
* @param maxLength the maximum length of the form to decode
* @param maxKeys the maximum number of keys to decode
* @throws IOException if unable to decode input stream
*/
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;
try(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(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
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();
int code1=in.read();
output.write(decodeHexChar(code0,code1));
break;
default:
output.write(c);
break;
}
totalLength++;
if (maxLength>=0 && totalLength > maxLength)
throw new IllegalStateException("Form is 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.
* @param encoded the encoded string to decode
* @return the decoded string
*/
public static String decodeString(String encoded)
{
return decodeString(encoded,0,encoded.length(),ENCODING);
}
/* -------------------------------------------------------------- */
/** Decode String with % encoding.
* This method makes the assumption that the majority of calls
* will need no decoding.
* @param encoded the encoded string to decode
* @param offset the offset in the encoded string to decode from
* @param length the length of characters in the encoded string to decode
* @param charset the charset to use for decoding
* @return the decoded string
*/
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();
}
}
private static char decodeHexChar(int hi, int lo)
{
try
{
return (char) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + (char) hi + (char) lo + "'");
}
}
private static byte decodeHexByte(char hi, char lo)
{
try
{
return (byte) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + hi + lo + "'");
}
}
/* ------------------------------------------------------------ */
/** Perform URL encoding.
* @param string the string to encode
* @return encoded string.
*/
public static String encodeString(String string)
{
return encodeString(string,ENCODING);
}
/* ------------------------------------------------------------ */
/** Perform URL encoding.
* @param string the string to encode
* @param charset the charset to use for encoding
* @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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy