/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package org.ireland.jnetty.util.http;
import com.caucho.util.CharBuffer;
import com.caucho.util.FreeList;
import com.caucho.vfs.ByteToChar;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.ireland.jnetty.config.ConfigException;
import org.ireland.jnetty.dispatch.HttpInvocation;
/**
* Decodes invocation URI.
*/
public class URIDecoder
{
private static boolean isWindows = true;
private static final Log log = LogFactory.getLog(URIDecoder.class.getName());
private static final FreeList<ByteToChar> _freeConverters = new FreeList<ByteToChar>(256);
// The character encoding
private String _encoding = "UTF-8";
private String _sessionCookie = "JSESSIONID";
private String _sslSessionCookie;
// The URL-encoded session suffix
private String _sessionSuffix = ";jsessionid=";
// The URL-encoded session prefix
private String _sessionPrefix;
private int _maxURILength = 1024;
/**
* Creates the invocation decoder.
*/
public URIDecoder()
{
_encoding = "UTF-8";
}
/**
* Returns the character encoding.
*/
public String getEncoding()
{
return _encoding;
}
/**
* Sets the character encoding.
*/
public void setEncoding(String encoding)
{
_encoding = encoding;
}
/**
* Sets the session cookie
*/
public void setSessionCookie(String cookie)
{
_sessionCookie = cookie;
}
/**
* Gets the session cookie
*/
public String getSessionCookie()
{
return _sessionCookie;
}
/**
* Sets the SSL session cookie
*/
public void setSSLSessionCookie(String cookie)
{
_sslSessionCookie = cookie;
}
/**
* Gets the SSL session cookie
*/
public String getSSLSessionCookie()
{
if (_sslSessionCookie != null)
return _sslSessionCookie;
else
return _sessionCookie;
}
/**
* Sets the session url prefix.
*/
public void setSessionURLPrefix(String prefix)
{
_sessionSuffix = prefix;
}
/**
* Gets the session url prefix.
*/
public String getSessionURLPrefix()
{
return _sessionSuffix;
}
/**
* Sets the alternate session url prefix.
*/
public void setAlternateSessionURLPrefix(String prefix) throws ConfigException
{
if (!prefix.startsWith("/"))
prefix = '/' + prefix;
if (prefix.lastIndexOf('/') > 0)
throw new ConfigException("'"+prefix+"' is an invalidate alternate-session-url-prefix. The url-prefix must not have any embedded '/'.");
_sessionPrefix = prefix;
_sessionSuffix = null;
}
/**
* Gets the session url prefix.
*/
public String getAlternateSessionURLPrefix()
{
return _sessionPrefix;
}
public int getMaxURILength()
{
return _maxURILength;
}
public void setMaxURILength(int maxURILength)
{
_maxURILength = maxURILength;
}
/**
* Splits out the query string and unescape the value.
*/
public void splitQueryAndUnescape(HttpInvocation invocation, byte[] rawURI, int uriLength) throws IOException
{
for (int i = 0; i < uriLength; i++)
{
if (rawURI[i] == '?')
{
i++;
// XXX: should be the host encoding?
String queryString = byteToChar(rawURI, i, uriLength - i, "ISO-8859-1");
invocation.setQueryString(queryString);
uriLength = i - 1;
break;
}
}
String rawURIString = byteToChar(rawURI, 0, uriLength, "ISO-8859-1");
invocation.setRawContextURI(rawURIString);
String decodedURI = normalizeUriEscape(rawURI, 0, uriLength, _encoding);
if (_sessionSuffix != null)
{
int p = decodedURI.indexOf(_sessionSuffix);
if (p >= 0)
{
int suffixLength = _sessionSuffix.length();
int tail = decodedURI.indexOf(';', p + suffixLength);
String sessionId;
if (tail > 0)
sessionId = decodedURI.substring(p + suffixLength, tail);
else
sessionId = decodedURI.substring(p + suffixLength);
decodedURI = decodedURI.substring(0, p);
invocation.setSessionId(sessionId);
p = rawURIString.indexOf(_sessionSuffix);
if (p > 0)
{
rawURIString = rawURIString.substring(0, p);
invocation.setRawContextURI(rawURIString);
}
}
}
else if (_sessionPrefix != null)
{
if (decodedURI.startsWith(_sessionPrefix))
{
int prefixLength = _sessionPrefix.length();
int tail = decodedURI.indexOf('/', prefixLength);
String sessionId;
if (tail > 0)
{
sessionId = decodedURI.substring(prefixLength, tail);
decodedURI = decodedURI.substring(tail);
invocation.setRawContextURI(rawURIString.substring(tail));
}
else
{
sessionId = decodedURI.substring(prefixLength);
decodedURI = "/";
invocation.setRawContextURI("/");
}
invocation.setSessionId(sessionId);
}
}
String uri = normalizeUri(decodedURI);
invocation.setContextURI(uri);
}
/**
* Splits out the query string, and normalizes the URI, assuming nothing needs unescaping.
*/
public void splitQuery(HttpInvocation invocation, String rawContextURI)
{
invocation.setRawContextURI(rawContextURI);
int p = rawContextURI.indexOf('?');
if (p > 0)
{
//设置URI上的参数
invocation.setQueryString(rawContextURI.substring(p + 1));
rawContextURI = rawContextURI.substring(0, p);
}
String contextURI = normalizeUri(rawContextURI);
invocation.setContextURI(contextURI);
}
/**
* Splits out the query string, return the URI that without Querying String
*/
public static String cutQuery(String rawURI)
{
int p = rawURI.indexOf('?');
if (p > 0)
return rawURI.substring(0, p);
else
return rawURI;
}
/**
* Just normalize the URI.
*/
public void normalizeURI(HttpInvocation invocation, String rawURI) throws IOException
{
invocation.setRawContextURI(rawURI);
String uri = normalizeUri(rawURI);
invocation.setContextURI(uri);
}
private String byteToChar(byte[] buffer, int offset, int length, String encoding)
{
ByteToChar converter = allocateConverter();
// XXX: make this configurable
if (encoding == null)
encoding = "utf-8";
try
{
converter.setEncoding(encoding);
}
catch (UnsupportedEncodingException e)
{
log.debug( e.toString(), e);
}
String result;
try
{
for (; length > 0; length--)
converter.addByte(buffer[offset++]);
result = converter.getConvertedString();
freeConverter(converter);
}
catch (IOException e)
{
result = "unknown";
}
return result;
}
/**
* Normalize a uri to remove '///', '/./', 'foo/..', etc.
*
* @param uri
* the raw uri to be normalized
* @return a normalized URI
*/
public String normalizeUri(String uri)
{
return normalizeUri(uri, this.isWindows);
}
/**
* Normalize a uri to remove '///', '/./', 'foo/..', etc.
*
* @param uri
* the raw uri to be normalized
* @return a normalized URI
*/
public String normalizeUri(String uri, boolean isWindows)
{
CharBuffer cb = new CharBuffer();
int len = uri.length();
if (_maxURILength < len)
throw new IllegalArgumentException("The request contains an illegal URL because it is too long.");
char ch;
if (len == 0 || (ch = uri.charAt(0)) != '/' && ch != '\\')
cb.append('/');
for (int i = 0; i < len; i++)
{
ch = uri.charAt(i);
if (ch == '/' || ch == '\\')
{
dots: while (i + 1 < len)
{
ch = uri.charAt(i + 1);
if (ch == '/' || ch == '\\')
i++;
else if (ch != '.')
break dots;
else if (len <= i + 2 || (ch = uri.charAt(i + 2)) == '/' || ch == '\\')
{
i += 2;
}
else if (ch != '.')
break dots;
else if (len <= i + 3 || (ch = uri.charAt(i + 3)) == '/' || ch == '\\')
{
int j;
for (j = cb.length() - 1; j >= 0; j--)
{
if ((ch = cb.charAt(j)) == '/' || ch == '\\')
break;
}
if (j > 0)
cb.setLength(j);
else
cb.setLength(0);
i += 3;
}
else
{
throw new IllegalArgumentException("The request contains an illegal URL.");
}
}
while (isWindows && cb.getLength() > 0 && ((ch = cb.getLastChar()) == '.' || ch == ' '))
{
cb.setLength(cb.getLength() - 1);
if (cb.getLength() > 0 && (ch = cb.getLastChar()) == '/' || ch == '\\')
{
cb.setLength(cb.getLength() - 1);
// server/003n
continue;
}
}
cb.append('/');
}
else if (ch == 0)
throw new IllegalArgumentException("The request contains an illegal URL.");
else
cb.append(ch);
}
while (isWindows && cb.getLength() > 0 && ((ch = cb.getLastChar()) == '.' || ch == ' '))
{
cb.setLength(cb.getLength() - 1);
}
return cb.toString();
}
/**
* Converts the escaped URI to a string.
*
* @param rawUri
* the escaped URI
* @param i
* index into the URI
* @param len
* the length of the uri
* @param encoding
* the character encoding to handle %xx
*
* @return the converted URI
*/
private static String normalizeUriEscape(byte[] rawUri, int i, int len, String encoding) throws IOException
{
ByteToChar converter = allocateConverter();
// XXX: make this configurable
if (encoding == null)
encoding = "utf-8";
try
{
converter.setEncoding(encoding);
}
catch (UnsupportedEncodingException e)
{
log.debug( e.toString(), e);
}
try
{
while (i < len)
{
int ch = rawUri[i++] & 0xff;
if (ch == '%')
i = scanUriEscape(converter, rawUri, i, len);
else
converter.addByte(ch);
}
String result = converter.getConvertedString();
freeConverter(converter);
return result;
}
catch (Exception e)
{
throw new IllegalArgumentException("The URL contains escaped bytes unsupported by the '"+encoding+"' encoding.");
}
}
/**
* Scans the next character from URI, adding it to the converter.
*
* @param converter
* the byte-to-character converter
* @param rawUri
* the raw URI
* @param i
* index into the URI
* @param len
* the raw URI length
*
* @return next index into the URI
*/
private static int scanUriEscape(ByteToChar converter, byte[] rawUri, int i, int len) throws IOException
{
int ch1 = i < len ? (rawUri[i++] & 0xff) : -1;
if (ch1 == 'u')
{
ch1 = i < len ? (rawUri[i++] & 0xff) : -1;
int ch2 = i < len ? (rawUri[i++] & 0xff) : -1;
int ch3 = i < len ? (rawUri[i++] & 0xff) : -1;
int ch4 = i < len ? (rawUri[i++] & 0xff) : -1;
converter.addChar((char) ((toHex(ch1) << 12) + (toHex(ch2) << 8) + (toHex(ch3) << 4) + (toHex(ch4))));
}
else
{
int ch2 = i < len ? (rawUri[i++] & 0xff) : -1;
int b = (toHex(ch1) << 4) + toHex(ch2);
;
converter.addByte(b);
}
return i;
}
/**
* Convert a character to hex
*/
private static int toHex(int ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
else
return -1;
}
private static ByteToChar allocateConverter()
{
ByteToChar converter = _freeConverters.allocate();
if (converter == null)
converter = ByteToChar.create();
else
converter.clear();
return converter;
}
private static void freeConverter(ByteToChar converter)
{
_freeConverters.free(converter);
}
}