/* * 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); } }