org.ireland.jnetty.util.http.URIDecoder.java Source code

Java tutorial

Introduction

Here is the source code for org.ireland.jnetty.util.http.URIDecoder.java

Source

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