net.lightbody.bmp.proxy.jetty.http.HttpRequest.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.http.HttpRequest.java

Source

// ========================================================================
// $Id: HttpRequest.java,v 1.91 2006/11/23 08:56:52 gregwilkins Exp $
// Copyright 1996-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package net.lightbody.bmp.proxy.jetty.http;

import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.*;
import org.apache.commons.logging.Log;

import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.InetAddress;
import java.security.Principal;
import java.util.*;

/* ------------------------------------------------------------ */
/**
 * HTTP Request. This class manages the headers, trailers and content streams of a HTTP request. It
 * can be used for receiving or generating requests.
 * <P>
 * This class is not synchronized. It should be explicitly synchronized if it is used by multiple
 * threads.
 * 
 * @see HttpResponse
 * @version $Id: HttpRequest.java,v 1.91 2006/11/23 08:56:52 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class HttpRequest extends HttpMessage {
    private static Log log = LogFactory.getLog(HttpRequest.class);

    /* ------------------------------------------------------------ */
    /**
     * Request METHODS.
     */
    public static final String __GET = "GET", __POST = "POST", __HEAD = "HEAD", __PUT = "PUT",
            __OPTIONS = "OPTIONS", __DELETE = "DELETE", __TRACE = "TRACE", __CONNECT = "CONNECT", __MOVE = "MOVE";

    /* ------------------------------------------------------------ */
    /**
     * Max size of the form content. Limits the size of the data a client can push at the server.
     * Set via the org.mortbay.http.HttpRequest.maxContentSize system property.
     */
    public static int __maxFormContentSize = Integer
            .getInteger("net.lightbody.bmp.proxy.jetty.http.HttpRequest.maxFormContentSize", 200000).intValue();

    /* ------------------------------------------------------------ */
    /**
     * Maximum header line length.
     */
    public static int __maxLineLength = 4096;

    public static final StringMap __methodCache = new StringMap(true);
    public static final StringMap __versionCache = new StringMap(true);
    static {
        __methodCache.put(__GET, null);
        __methodCache.put(__POST, null);
        __methodCache.put(__HEAD, null);
        __methodCache.put(__PUT, null);
        __methodCache.put(__OPTIONS, null);
        __methodCache.put(__DELETE, null);
        __methodCache.put(__TRACE, null);
        __methodCache.put(__CONNECT, null);
        __methodCache.put(__MOVE, null);

        __versionCache.put(__HTTP_1_1, null);
        __versionCache.put(__HTTP_1_0, null);
        __versionCache.put(__HTTP_0_9, null);
    }

    private static Cookie[] __noCookies = new Cookie[0];

    /* ------------------------------------------------------------ */
    private String _method = null;
    private URI _uri = null;
    private String _host;
    private String _hostPort;
    private int _port;
    private List _te;
    private MultiMap _parameters;
    private boolean _paramsExtracted;
    private boolean _handled;
    private Cookie[] _cookies;
    private String[] _lastCookies;
    private boolean _cookiesExtracted;
    private long _timeStamp;
    private String _timeStampStr;
    private Principal _userPrincipal;
    private String _authUser;
    private String _authType;
    private char[] _uriExpanded;

    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     */
    public HttpRequest() {
    }

    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     * 
     * @param connection
     */
    public HttpRequest(HttpConnection connection) {
        super(connection);
    }

    /* ------------------------------------------------------------ */
    /**
     * Get Request TimeStamp
     * 
     * @return The time that the request was received.
     */
    public String getTimeStampStr() {
        if (_timeStampStr == null && _timeStamp > 0)
            _timeStampStr = HttpFields.__dateCache.format(_timeStamp);
        return _timeStampStr;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get Request TimeStamp
     * 
     * @return The time that the request was received.
     */
    public long getTimeStamp() {
        return _timeStamp;
    }

    /* ------------------------------------------------------------ */
    public void setTimeStamp(long ts) {
        _timeStamp = ts;
    }

    /* ------------------------------------------------------------ */
    /**
     * @deprecated use getHttpResponse()
     */
    public HttpResponse getResponse() {
        return getHttpResponse();
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the HTTP Response. Get the HTTP Response associated with this request.
     * 
     * @return associated response
     */
    public HttpResponse getHttpResponse() {
        if (_connection == null)
            return null;
        return _connection.getResponse();
    }

    /* ------------------------------------------------------------ */
    /**
     * Is the request handled.
     * 
     * @return True if the request has been set to handled or the associated response is not
     *         editable.
     */
    public boolean isHandled() {
        if (_handled)
            return true;

        HttpResponse response = getHttpResponse();
        return (response != null && response.getState() != HttpMessage.__MSG_EDITABLE);
    }

    /* ------------------------------------------------------------ */
    /**
     * Set the handled status.
     * 
     * @param handled true or false
     */
    public void setHandled(boolean handled) {
        _handled = handled;
    }

    /* ------------------------------------------------------------ */
    /**
     * Read the request line and header.
     * 
     * @param in
     * @exception IOException
     */
    public void readHeader(LineInput in) throws IOException {
        _state = __MSG_BAD;

        // Get start line
        net.lightbody.bmp.proxy.jetty.util.LineInput.LineBuffer line_buffer;

        do {
            line_buffer = in.readLineBuffer();
            if (line_buffer == null)
                throw new EOFException();
        } while (line_buffer.size == 0);

        if (line_buffer.size >= __maxLineLength)
            throw new HttpException(HttpResponse.__414_Request_URI_Too_Large);
        decodeRequestLine(line_buffer.buffer, line_buffer.size);
        _timeStamp = System.currentTimeMillis();

        // Handle version - replace with fast compare
        if (__HTTP_1_1.equals(_version)) {
            _dotVersion = 1;
            _version = __HTTP_1_1;
            _header.read(in);
            updateMimeType();
        } else if (__HTTP_0_9.equals(_version)) {
            _dotVersion = -1;
            _version = __HTTP_0_9;
        } else {
            _dotVersion = 0;
            _version = __HTTP_1_0;
            _header.read(in);
            updateMimeType();
        }

        _handled = false;
        _state = __MSG_RECEIVED;
    }

    /* -------------------------------------------------------------- */
    /**
     * Write the HTTP request line as it was received.
     */
    public void writeRequestLine(Writer writer) throws IOException {
        writer.write(_method);
        writer.write(' ');
        writer.write(_uri != null ? _uri.toString() : "null");
        writer.write(' ');
        writer.write(_version);
    }

    /* -------------------------------------------------------------- */
    /**
     * Write the request header. Places the message in __MSG_SENDING state.
     * 
     * @param writer Http output stream
     * @exception IOException IO problem
     */
    public void writeHeader(Writer writer) throws IOException {
        if (_state != __MSG_EDITABLE)
            throw new IllegalStateException("Not MSG_EDITABLE");

        _state = __MSG_BAD;
        writeRequestLine(writer);
        writer.write(HttpFields.__CRLF);
        _header.write(writer);
        _state = __MSG_SENDING;
    }

    /* -------------------------------------------------------------- */
    /**
     * Return the HTTP request line as it was received.
     */
    public String getRequestLine() {
        return _method + " " + _uri + " " + _version;
    }

    /* -------------------------------------------------------------- */
    /**
     * Get the HTTP method for this request. Returns the method with which the request was made. The
     * returned value can be "GET", "HEAD", "POST", or an extension method. Same as the CGI variable
     * REQUEST_METHOD.
     * 
     * @return The method
     */
    public String getMethod() {
        return _method;
    }

    /* ------------------------------------------------------------ */
    public void setMethod(String method) {
        if (getState() != __MSG_EDITABLE)
            throw new IllegalStateException("Not EDITABLE");
        _method = method;
    }

    /* ------------------------------------------------------------ */
    public String getVersion() {
        return _version;
    }

    /* ------------------------------------------------------------ */
    /**
     * Reconstructs the URL the client used to make the request. The returned URL contains a
     * protocol, server name, port number, and, but it does not include a path.
     * <p>
     * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the
     * URL easily, for example, to append path and query parameters.
     * 
     * This method is useful for creating redirect messages and for reporting errors.
     * 
     * @return "scheme://host:port"
     */
    public StringBuffer getRootURL() {
        StringBuffer url = new StringBuffer(48);
        synchronized (url) {
            String scheme = getScheme();
            int port = getPort();

            url.append(scheme);
            url.append("://");
            if (_hostPort != null)
                url.append(_hostPort);
            else {
                url.append(getHost());
                if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80)
                        || (scheme.equalsIgnoreCase("https") && port != 443))) {
                    url.append(':');
                    url.append(port);
                }
            }
            return url;
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Reconstructs the URL the client used to make the request. The returned URL contains a
     * protocol, server name, port number, and server path, but it does not include query string
     * parameters.
     * 
     * <p>
     * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the
     * URL easily, for example, to append query parameters.
     * 
     * <p>
     * This method is useful for creating redirect messages and for reporting errors.
     * 
     * @return a <code>StringBuffer</code> object containing the reconstructed URL
     *  
     */
    public StringBuffer getRequestURL() {
        StringBuffer buf = getRootURL();
        buf.append(getPath());
        return buf;
    }

    /* -------------------------------------------------------------- */
    /**
     * Get the full URI.
     * 
     * @return the request URI (not a clone).
     */
    public URI getURI() {
        return _uri;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the request Scheme. The scheme is obtained from an absolute URI. If the URI in the
     * request is not absolute, then the connections default scheme is returned. If there is no
     * connection "http" is returned.
     * 
     * @return The request scheme (eg. "http", "https", etc.)
     */
    public String getScheme() {
        String scheme = _uri.getScheme();
        if (scheme == null && _connection != null)
            scheme = _connection.getDefaultScheme();
        return scheme == null ? "http" : scheme;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return True if this request came over an integral channel such as SSL
     */
    public boolean isIntegral() {
        return _connection.getListener().isIntegral(_connection);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return True if this request came over an confidential channel such as SSL.
     */
    public boolean isConfidential() {
        return _connection.getListener().isConfidential(_connection);
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the request host.
     * 
     * @return The host name obtained from an absolute URI, the HTTP header field, the requests
     *         connection or the local host name.
     */
    public String getHost() {
        // Return already determined host
        if (_host != null)
            return _host;

        // Return host from absolute URI
        _host = _uri.getHost();
        _port = _uri.getPort();
        if (_host != null)
            return _host;

        // Return host from header field
        _hostPort = _header.get(HttpFields.__Host);
        _host = _hostPort;
        _port = 0;
        if (_host != null && _host.length() > 0) {
            int colon = _host.lastIndexOf(':');
            if (colon >= 0) {
                if (colon < _host.length()) {
                    try {
                        _port = TypeUtil.parseInt(_host, colon + 1, -1, 10);
                    } catch (Exception e) {
                        LogSupport.ignore(log, e);
                    }
                }
                _host = _host.substring(0, colon);
            }

            return _host;
        }

        // Return host from connection
        if (_connection != null) {
            _host = _connection.getServerName();
            _port = _connection.getServerPort();
            if (_host != null && !InetAddrPort.__0_0_0_0.equals(_host))
                return _host;
        }

        // Return the local host
        try {
            _host = InetAddress.getLocalHost().getHostAddress();
        } catch (java.net.UnknownHostException e) {
            LogSupport.ignore(log, e);
        }
        return _host;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the request port. The port is obtained either from an absolute URI, the HTTP Host header
     * field, the connection or the default.
     * 
     * @return The port. 0 should be interpreted as the default port.
     */
    public int getPort() {
        if (_port > 0)
            return _port;
        if (_host != null)
            return 0;
        if (_uri.isAbsolute())
            _port = _uri.getPort();
        else if (_connection != null)
            _port = _connection.getServerPort();
        return _port;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the request path.
     * 
     * @return The URI path of the request.
     */
    public String getPath() {
        return _uri.getPath();
    }

    /* ------------------------------------------------------------ */
    public void setPath(String path) {
        if (getState() != __MSG_EDITABLE)
            throw new IllegalStateException("Not EDITABLE");
        if (_uri == null)
            _uri = new URI(path);
        else
            _uri.setURI(path);
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the encoded request path.
     * 
     * @return The path with % encoding.
     */
    public String getEncodedPath() {
        return _uri.getEncodedPath();
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the request query.
     * 
     * @return the request query excluding the '?'
     */
    public String getQuery() {
        return _uri.getQuery();
    }

    /* ------------------------------------------------------------ */
    public void setQuery(String q) {
        if (getState() != __MSG_EDITABLE)
            throw new IllegalStateException("Not EDITABLE");
        _uri.setQuery(q);
    }

    /* ------------------------------------------------------------ */
    public String getRemoteAddr() {
        String addr = "127.0.0.1";
        HttpConnection connection = getHttpConnection();
        if (connection != null) {
            addr = connection.getRemoteAddr();
            if (addr == null)
                addr = connection.getRemoteHost();
        }
        return addr;
    }

    /* ------------------------------------------------------------ */
    public String getRemoteHost() {
        String host = "127.0.0.1";
        HttpConnection connection = getHttpConnection();
        if (connection != null) {
            host = connection.getRemoteHost();
            if (host == null)
                host = connection.getRemoteAddr();
        }
        return host;
    }

    /* ------------------------------------------------------------ */
    /**
     * Decode HTTP request line.
     * 
     * @param buf Character buffer
     * @param len Length of line in buffer.
     * @exception IOException
     */
    void decodeRequestLine(char[] buf, int len) throws IOException {
        // Search for first space separated chunk
        int s1 = -1, s2 = -1, s3 = -1;
        int state = 0;
        startloop: for (int i = 0; i < len; i++) {
            char c = buf[i];
            switch (state) {
            case 0: // leading white
                if (c == ' ')
                    continue;
                state = 1;
                s1 = i;

            case 1: // reading method
                if (c == ' ')
                    state = 2;
                else {
                    s2 = i;
                    if (c >= 'a' && c <= 'z')
                        buf[i] = (char) (c - 'a' + 'A');
                }
                continue;

            case 2: // skip whitespace after method
                s3 = i;
                if (c != ' ')
                    break startloop;
            }
        }

        // Search for last space separated chunk
        int e1 = -1, e2 = -1, e3 = -1;
        state = 0;
        endloop: for (int i = len; i-- > 0;) {
            char c = buf[i];
            switch (state) {
            case 0: // trailing white
                if (c == ' ')
                    continue;
                state = 1;
                e1 = i;

            case 1: // reading Version
                if (c == ' ')
                    state = 2;
                else
                    e2 = i;
                continue;

            case 2: // skip whitespace before version
                e3 = i;
                if (c != ' ')
                    break endloop;
            }
        }

        // Check sufficient params
        if (s3 < 0 || e1 < 0 || e3 < s2)
            throw new IOException("Bad Request: " + new String(buf, 0, len));

        // get method
        Map.Entry method = __methodCache.getEntry(buf, s1, s2 - s1 + 1);
        if (method != null)
            _method = (String) method.getKey();
        else
            _method = new String(buf, s1, s2 - s1 + 1).toUpperCase();

        // get version as uppercase
        if (s2 != e3 || s3 != e2) {
            Map.Entry version = __versionCache.getEntry(buf, e2, e1 - e2 + 1);
            if (version != null)
                _version = (String) version.getKey();
            else {
                for (int i = e2; i <= e1; i++)
                    if (buf[i] >= 'a' && buf[i] <= 'z')
                        buf[i] = (char) (buf[i] - 'a' + 'A');
                _version = new String(buf, e2, e1 - e2 + 1);
            }
        } else {
            // missing version
            _version = __HTTP_0_9;
            e3 = e1;
        }

        // handle URI
        try {
            String raw_uri = null;
            if (URI.__CHARSET_IS_DEFAULT)
                raw_uri = new String(buf, s3, e3 - s3 + 1);
            else {
                int l = e3 - s3 + 1;
                for (int i = 0; i < l; i++) {
                    char c = buf[s3 + i];

                    if (c >= 0 && c < 0x80)
                        continue;

                    if (_uriExpanded == null || _uriExpanded.length < 3 * l)
                        _uriExpanded = new char[3 * l];

                    if (i > 0)
                        System.arraycopy(buf, s3, _uriExpanded, 0, i);
                    int j = i;
                    for (; i < l; i++) {
                        c = buf[s3 + i];
                        if (c >= 0 && c < 0x80)
                            _uriExpanded[j++] = c;
                        else {
                            _uriExpanded[j++] = '%';
                            _uriExpanded[j++] = TypeUtil.toHexChar(0xf & (c >> 4));
                            _uriExpanded[j++] = TypeUtil.toHexChar(0xf & c);
                        }
                    }
                    raw_uri = new String(_uriExpanded, 0, j);
                }

                if (raw_uri == null)
                    raw_uri = new String(buf, s3, e3 - s3 + 1);
            }

            if (_uri == null)
                _uri = new URI(raw_uri);
            else
                _uri.setURI(raw_uri);
        } catch (IllegalArgumentException e) {
            LogSupport.ignore(log, e);
            throw new HttpException(HttpResponse.__400_Bad_Request, new String(buf, s3, e3 - s3 + 1));
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Force a removeField. This call ignores the message state and forces a field to be removed
     * from the request. It is required for the handling of the Connection field.
     * 
     * @param name The field name
     * @return The old value or null.
     */
    Object forceRemoveField(String name) {
        int saved_state = _state;
        try {
            _state = __MSG_EDITABLE;
            return removeField(name);
        } finally {
            _state = saved_state;
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the acceptable transfer encodings. The TE field is used to construct a list of acceptable
     * extension transfer codings in quality order. An empty list implies that only "chunked" is
     * acceptable. A null list implies that no transfer coding can be applied.
     * 
     * If the "trailer" coding is found in the TE field, then message trailers are enabled in any
     * linked response.
     * 
     * @return List of codings.
     */
    public List getAcceptableTransferCodings() {
        if (_dotVersion < 1)
            return null;
        if (_te != null)
            return _te;

        // Decode any TE field
        Enumeration tenum = getFieldValues(HttpFields.__TE, HttpFields.__separators);

        if (tenum != null) {
            // Sort the list
            List te = HttpFields.qualityList(tenum);
            int size = te.size();
            // Process if something there
            if (size > 0) {
                Object acceptable = null;

                // remove trailer and chunked items.
                ListIterator iter = te.listIterator();
                while (iter.hasNext()) {
                    String coding = StringUtil
                            .asciiToLowerCase(HttpFields.valueParameters(iter.next().toString(), null));

                    if (!HttpFields.__Chunked.equalsIgnoreCase(coding)) {
                        acceptable = LazyList.ensureSize(acceptable, size);
                        acceptable = LazyList.add(acceptable, coding);
                    }
                }
                _te = LazyList.getList(acceptable);
            } else
                _te = Collections.EMPTY_LIST;
        } else
            _te = Collections.EMPTY_LIST;

        return _te;
    }

    /* ------------------------------------------------------------ */
    /*
     * Extract Paramters from query string and/or form content.
     */
    private void extractParameters() {
        if (_paramsExtracted)
            return;
        _paramsExtracted = true;

        if (_parameters == null)
            _parameters = new MultiMap(16);

        // Handle query string
        String encoding = getCharacterEncoding();
        if (encoding == null) {
            _uri.putParametersTo(_parameters);
        } else {
            // An encoding has been set, so reencode query string.
            String query = _uri.getQuery();
            if (query != null)
                UrlEncoded.decodeTo(query, _parameters, encoding);
        }

        // handle any content.
        if (_state == __MSG_RECEIVED) {
            String content_type = getField(HttpFields.__ContentType);
            if (content_type != null && content_type.length() > 0) {
                content_type = StringUtil.asciiToLowerCase(content_type);
                content_type = HttpFields.valueParameters(content_type, null);

                if (HttpFields.__WwwFormUrlEncode.equalsIgnoreCase(content_type)
                        && HttpRequest.__POST.equals(getMethod())) {
                    int content_length = getIntField(HttpFields.__ContentLength);
                    if (content_length == 0)
                        log.debug("No form content");
                    else {
                        try {
                            int max = content_length;
                            if (__maxFormContentSize > 0) {
                                if (max < 0)
                                    max = __maxFormContentSize;
                                else if (max > __maxFormContentSize)
                                    throw new IllegalStateException("Form too large");
                            }

                            // Read the content
                            ByteArrayOutputStream2 bout = new ByteArrayOutputStream2(max > 0 ? max : 4096);
                            InputStream in = getInputStream();

                            // Copy to a byte array.
                            // TODO - this is very inefficient and we could
                            // save lots of memory by streaming this!!!!
                            IO.copy(in, bout, max);

                            if (bout.size() == __maxFormContentSize && in.available() > 0)
                                throw new IllegalStateException("Form too large");

                            // Add form params to query params
                            UrlEncoded.decodeTo(bout.getBuf(), 0, bout.getCount(), _parameters, encoding);
                        } catch (EOFException e) {
                            LogSupport.ignore(log, e);
                        } catch (IOException e) {
                            if (log.isDebugEnabled())
                                log.warn(LogSupport.EXCEPTION, e);
                            else
                                log.warn(e.toString());
                        }
                    }
                }
            }
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Map of parameters
     */
    public MultiMap getParameters() {
        if (!_paramsExtracted)
            extractParameters();
        return _parameters;
    }

    /* ------------------------------------------------------------ */
    /**
     * Get the set of parameter names.
     * 
     * @return Set of parameter names.
     */
    public Set getParameterNames() {
        if (!_paramsExtracted)
            extractParameters();
        return _parameters.keySet();
    }

    /* ------------------------------------------------------------ */
    /**
     * Get a parameter value.
     * 
     * @param name Parameter name
     * @return Parameter value
     */
    public String getParameter(String name) {
        if (!_paramsExtracted)
            extractParameters();
        return (String) _parameters.getValue(name, 0);
    }

    /* ------------------------------------------------------------ */
    /**
     * Get multi valued paramater.
     * 
     * @param name Parameter name
     * @return Parameter values
     */
    public List getParameterValues(String name) {
        if (!_paramsExtracted)
            extractParameters();
        return _parameters.getValues(name);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Parameters as a map of String arrays
     */
    public Map getParameterStringArrayMap() {
        if (!_paramsExtracted)
            extractParameters();
        return _parameters.toStringArrayMap();
    }

    /* -------------------------------------------------------------- */
    /**
     * Extract received cookies from a header.
     * 
     * @return Array of Cookies.
     */
    public Cookie[] getCookies() {
        if (_cookies != null && _cookiesExtracted)
            return _cookies;

        try {
            // Handle no cookies
            if (!_header.containsKey(HttpFields.__Cookie)) {
                _cookies = __noCookies;
                _cookiesExtracted = true;
                _lastCookies = null;
                return _cookies;
            }

            // Check if cookie headers match last cookies
            if (_lastCookies != null) {
                int last = 0;
                Enumeration enm = _header.getValues(HttpFields.__Cookie);
                while (enm.hasMoreElements()) {
                    String c = (String) enm.nextElement();
                    if (last >= _lastCookies.length || !c.equals(_lastCookies[last])) {
                        _lastCookies = null;
                        break;
                    }
                    last++;
                }
                if (_lastCookies != null) {
                    _cookiesExtracted = true;
                    return _cookies;
                }
            }

            // Get ready to parse cookies (Expensive!!!)
            Object cookies = null;
            Object lastCookies = null;

            int version = 0;
            Cookie cookie = null;

            // For each cookie header
            Enumeration enm = _header.getValues(HttpFields.__Cookie);
            while (enm.hasMoreElements()) {
                // Save a copy of the unparsed header as cache.
                String hdr = enm.nextElement().toString();
                lastCookies = LazyList.add(lastCookies, hdr);

                // Parse the header
                QuotedStringTokenizer tok = new QuotedStringTokenizer(hdr, ",;", false, false);
                tok.setSingle(false);
                while (tok.hasMoreElements()) {
                    String c = (String) tok.nextElement();
                    if (c == null)
                        continue;
                    c = c.trim();

                    try {
                        String n;
                        String v;
                        int e = c.indexOf('=');
                        if (e > 0) {
                            n = c.substring(0, e);
                            v = c.substring(e + 1);
                        } else {
                            n = c;
                            v = "";
                        }

                        // Handle quoted values
                        if (version > 0)
                            v = StringUtil.unquote(v);

                        // Ignore $ names
                        if (n.startsWith("$")) {
                            if ("$version".equalsIgnoreCase(n))
                                version = Integer.parseInt(QuotedStringTokenizer.unquoteDouble(v));
                            else if ("$path".equalsIgnoreCase(n) && cookie != null)
                                cookie.setPath(v);
                            else if ("$domain".equalsIgnoreCase(n) && cookie != null)
                                cookie.setDomain(v);
                            continue;
                        }

                        v = URI.decodePath(v);
                        cookie = new Cookie(n, v);
                        if (version > 0)
                            cookie.setVersion(version);
                        cookies = LazyList.add(cookies, cookie);
                    } catch (Exception ex) {
                        LogSupport.ignore(log, ex);
                    }
                }
            }

            int l = LazyList.size(cookies);
            if (_cookies == null || _cookies.length != l)
                _cookies = new Cookie[l];
            for (int i = 0; i < l; i++)
                _cookies[i] = (Cookie) LazyList.get(cookies, i);
            _cookiesExtracted = true;

            l = LazyList.size(lastCookies);
            _lastCookies = new String[l];
            for (int i = 0; i < l; i++)
                _lastCookies[i] = (String) LazyList.get(lastCookies, i);

        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
        }

        return _cookies;
    }

    /* ------------------------------------------------------------ */
    public boolean isUserInRole(String role) {
        Principal principal = getUserPrincipal();
        if (principal != null) {
            UserRealm realm = getHttpResponse().getHttpContext().getRealm();
            if (realm != null)
                return realm.isUserInRole(principal, role);
        }
        return false;
    }

    /* ------------------------------------------------------------ */
    public String getAuthType() {
        if (_authType == null)
            getUserPrincipal();
        return _authType;
    }

    /* ------------------------------------------------------------ */
    public void setAuthType(String a) {
        _authType = a;
    }

    /* ------------------------------------------------------------ */
    public String getAuthUser() {
        if (_authUser == null)
            getUserPrincipal();
        return _authUser;
    }

    /* ------------------------------------------------------------ */
    public void setAuthUser(String user) {
        _authUser = user;
    }

    /* ------------------------------------------------------------ */
    public boolean hasUserPrincipal() {
        if (_userPrincipal == __NOT_CHECKED)
            getUserPrincipal();
        return _userPrincipal != null && _userPrincipal != __NO_USER;
    }

    /* ------------------------------------------------------------ */
    public Principal getUserPrincipal() {
        if (_userPrincipal == __NO_USER)
            return null;

        if (_userPrincipal == __NOT_CHECKED) {
            _userPrincipal = __NO_USER;

            // Try a lazy authentication
            HttpContext context = getHttpResponse().getHttpContext();
            if (context != null) {
                Authenticator auth = context.getAuthenticator();
                UserRealm realm = context.getRealm();
                if (realm != null && auth != null) {
                    try {
                        // TODO - should not need to recalculate this!
                        String pathInContext = getPath().substring(context.getContextPath().length());
                        auth.authenticate(realm, pathInContext, this, null);
                    } catch (Exception e) {
                        LogSupport.ignore(log, e);
                    }
                }
            }

            if (_userPrincipal == __NO_USER)
                return null;
        }
        return _userPrincipal;
    }

    /* ------------------------------------------------------------ */
    public void setUserPrincipal(Principal principal) {
        _userPrincipal = principal;
    }

    /* ------------------------------------------------------------ */
    /**
     * Recycle the request.
     */
    void recycle(HttpConnection connection) {
        _method = null;
        //_uri=null;
        _host = null;
        _hostPort = null;
        _port = 0;
        _te = null;
        if (_parameters != null)
            _parameters.clear();
        _paramsExtracted = false;
        _handled = false;
        _cookiesExtracted = false;
        _timeStamp = 0;
        _timeStampStr = null;
        _authUser = null;
        _authType = null;
        _userPrincipal = null;
        super.recycle(connection);
    }

    /* ------------------------------------------------------------ */
    /**
     * Destroy the request. Help the garbage collector by null everything that we can.
     */
    public void destroy() {
        _parameters = null;
        _method = null;
        _uri = null;
        _host = null;
        _hostPort = null;
        _te = null;
        _cookies = null;
        _lastCookies = null;
        _timeStampStr = null;
        _userPrincipal = null;
        _authUser = null;
        _authUser = null;
        if (_attributes != null)
            _attributes.clear();
        super.destroy();
    }

    static Principal __NO_USER = new Principal() {
        public String getName() {
            return null;
        }
    };
    static Principal __NOT_CHECKED = new Principal() {
        public String getName() {
            return null;
        }
    };

    public void setURI(URI uri) {
        _uri = uri;
    }
}