view src/org/eclipse/jetty/server/Request.java @ 840:0f53601ea489

remove ConcurrentHashSet
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 19 Sep 2016 14:20:18 -0600
parents 86338c0029a9
children fa6158f29c45
line wrap: on
line source

//
//  ========================================================================
//  Copyright (c) 1995-2014 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.server;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;

import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferUtil;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.NIOBuffer;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/* ------------------------------------------------------------ */
/**
 * Jetty Request.
 * <p>
 * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package.
 * </p>
 * <p>
 * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
 * request object to be as lightweight as possible and not actually implement any significant behavior. For example
 * <ul>
 *
 * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
 * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li>
 *
 * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code>
 * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
 * </ul>
 *
 * A request instance is created for each {@link AbstractHttpConnection} accepted by the server and recycled for each HTTP request received via that connection.
 * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
 *
 * <p>
 * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
 * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
 * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
 * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
 *
 *
 */
public class Request implements HttpServletRequest
{
	public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
	public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
	public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
	private static final Logger LOG = LoggerFactory.getLogger(Request.class);

	private static final String __ASYNC_FWD = "org.eclipse.asyncfwd";
	private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
	private static final int __NONE = 0, _STREAM = 1, __READER = 2;

	public static class MultiPartCleanerListener implements ServletRequestListener
	{

		@Override
		public void requestDestroyed(ServletRequestEvent sre)
		{
			//Clean up any tmp files created by MultiPartInputStream
			MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
			if (mpis != null)
			{
				ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);

				//Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
				if (context == sre.getServletContext())
				{
					try
					{
						mpis.deleteParts();
					}
					catch (MultiException e)
					{
						sre.getServletContext().log("Errors deleting multipart tmp files", e);
					}
				}
			}
		}

		@Override
		public void requestInitialized(ServletRequestEvent sre)
		{
			//nothing to do, multipart config set up by ServletHolder.handle()
		}
		
	}
	
	
	/* ------------------------------------------------------------ */
	public static Request getRequest(HttpServletRequest request)
	{
		if (request instanceof Request)
			return (Request)request;

		return AbstractHttpConnection.getCurrentConnection().getRequest();
	}
	protected final AsyncContinuation _async = new AsyncContinuation();
	private boolean _asyncSupported = true;
	private volatile Attributes _attributes;
	private MultiMap<String> _baseParameters;
	private String _characterEncoding;
	protected AbstractHttpConnection _connection;
	private ContextHandler.Context _context;
	private boolean _newContext;
	private String _contextPath;
	private CookieCutter _cookies;
	private boolean _cookiesExtracted = false;
	private DispatcherType _dispatcherType;
	private boolean _dns = false;
	private EndPoint _endp;
	private boolean _handled = false;
	private int _inputState = __NONE;
	private String _method;
	private MultiMap<String> _parameters;
	private boolean _paramsExtracted;
	private String _pathInfo;
	private int _port;
	private String _protocol = HttpVersions.HTTP_1_1;
	private String _queryEncoding;
	private String _queryString;
	private BufferedReader _reader;
	private String _readerEncoding;
	private String _remoteAddr;
	private String _remoteHost;
	private Object _requestAttributeListeners;
	private String _requestURI;
	private String _scheme = URIUtil.HTTP;
	private String _serverName;
	private String _servletPath;
	private long _timeStamp;
	private long _dispatchTime;

	private Buffer _timeStampBuffer;
	private HttpURI _uri;
	
	private MultiPartInputStream _multiPartInputStream; //if the request is a multi-part mime
	
	/* ------------------------------------------------------------ */
	public Request()
	{
	}

	/* ------------------------------------------------------------ */
	public Request(AbstractHttpConnection connection)
	{
		setConnection(connection);
	}

	/* ------------------------------------------------------------ */
	public void addEventListener(final EventListener listener)
	{
		if (listener instanceof ServletRequestAttributeListener)
			_requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
		if (listener instanceof ContinuationListener)
			throw new IllegalArgumentException(listener.getClass().toString());
		if (listener instanceof AsyncListener)
			throw new IllegalArgumentException(listener.getClass().toString());
	}

	/* ------------------------------------------------------------ */
	/**
	 * Extract Parameters from query string and/or form _content.
	 */
	public void extractParameters()
	{
		if (_baseParameters == null)
			_baseParameters = new MultiMap(16);

		if (_paramsExtracted)
		{
			if (_parameters == null)
				_parameters = _baseParameters;
			return;
		}

		_paramsExtracted = true;

		try
		{
			// Handle query string
			if (_uri != null && _uri.hasQuery())
			{
				if (_queryEncoding == null)
					_uri.decodeQueryTo(_baseParameters);
				else
				{
					try
					{
						_uri.decodeQueryTo(_baseParameters,_queryEncoding);
					}
					catch (UnsupportedEncodingException e)
					{
						if (LOG.isDebugEnabled())
							LOG.warn("",e);
						else
							LOG.warn(e.toString());
					}
				}
			}

			// handle any _content.
			String encoding = getCharacterEncoding();
			String content_type = getContentType();
			if (content_type != null && content_type.length() > 0)
			{
				content_type = HttpFields.valueParameters(content_type,null);

				if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState == __NONE
						&& (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
				{
					int content_length = getContentLength();
					if (content_length != 0)
					{
						try
						{
							int maxFormContentSize = -1;
							int maxFormKeys = -1;

							if (_context != null)
							{
								maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
								maxFormKeys = _context.getContextHandler().getMaxFormKeys();
							}
							
							if (maxFormContentSize < 0)
							{
								Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
								if (obj == null)
									maxFormContentSize = 200000;
								else if (obj instanceof Number)
								{                      
									Number size = (Number)obj;
									maxFormContentSize = size.intValue();
								}
								else if (obj instanceof String)
								{
									maxFormContentSize = Integer.valueOf((String)obj);
								}
							}
							
							if (maxFormKeys < 0)
							{
								Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
								if (obj == null)
									maxFormKeys = 1000;
								else if (obj instanceof Number)
								{
									Number keys = (Number)obj;
									maxFormKeys = keys.intValue();
								}
								else if (obj instanceof String)
								{
									maxFormKeys = Integer.valueOf((String)obj);
								}
							}

							if (content_length > maxFormContentSize && maxFormContentSize > 0)
							{
								throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
							}
							InputStream in = getInputStream();

							// Add form params to query params
							UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
						}
						catch (IOException e)
						{
							if (LOG.isDebugEnabled())
								LOG.warn("",e);
							else
								LOG.warn(e.toString());
						}
					}
				}
			  
			}

			if (_parameters == null)
				_parameters = _baseParameters;
			else if (_parameters != _baseParameters)
			{
				// Merge parameters (needed if parameters extracted after a forward).
				Iterator iter = _baseParameters.entrySet().iterator();
				while (iter.hasNext())
				{
					Map.Entry entry = (Map.Entry)iter.next();
					String name = (String)entry.getKey();
					Object values = entry.getValue();
					for (int i = 0; i < LazyList.size(values); i++)
						_parameters.add(name,LazyList.get(values,i));
				}
			}

			if (content_type != null && content_type.length()>0 && content_type.startsWith("multipart/form-data") && getAttribute(__MULTIPART_CONFIG_ELEMENT)!=null)
			{
				try
				{
					getParts();
				}
				catch (IOException e)
				{
					if (LOG.isDebugEnabled())
						LOG.warn("",e);
					else
						LOG.warn(e.toString());
				}
				catch (ServletException e)
				{
					if (LOG.isDebugEnabled())
						LOG.warn("",e);
					else
						LOG.warn(e.toString());
				}
			}
		}
		finally
		{
			// ensure params always set (even if empty) after extraction
			if (_parameters == null)
				_parameters = _baseParameters;
		}
	}

	/* ------------------------------------------------------------ */
	public AsyncContext getAsyncContext()
	{
		if (_async.isInitial() && !_async.isAsyncStarted())
			throw new IllegalStateException(_async.getStatusString());
		return _async;
	}

	/* ------------------------------------------------------------ */
	public AsyncContinuation getAsyncContinuation()
	{
		return _async;
	}
	
	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
	 */
	public Object getAttribute(String name)
	{
		if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
			return new Long(getConnection().getEndPoint().getMaxIdleTime());

		Object attr = (_attributes == null)?null:_attributes.getAttribute(name);
		if (attr == null && Continuation.ATTRIBUTE.equals(name))
			return _async;
		return attr;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getAttributeNames()
	 */
	public Enumeration getAttributeNames()
	{
		if (_attributes == null)
			return Collections.enumeration(Collections.EMPTY_LIST);

		return AttributesMap.getAttributeNamesCopy(_attributes);
	}

	/* ------------------------------------------------------------ */
	/*
	 */
	public Attributes getAttributes()
	{
		if (_attributes == null)
			_attributes = new AttributesMap();
		return _attributes;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getAuthType()
	 */
	public String getAuthType()
	{
		return null;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getCharacterEncoding()
	 */
	public String getCharacterEncoding()
	{
		return _characterEncoding;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the connection.
	 */
	public AbstractHttpConnection getConnection()
	{
		return _connection;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getContentLength()
	 */
	public int getContentLength()
	{
		return (int)_connection.getRequestFields().getLongField(HttpHeaders.CONTENT_LENGTH_BUFFER);
	}

	public long getContentRead()
	{
		if (_connection == null || _connection.getParser() == null)
			return -1;

		return ((HttpParser)_connection.getParser()).getContentRead();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getContentType()
	 */
	public String getContentType()
	{
		return _connection.getRequestFields().getStringField(HttpHeaders.CONTENT_TYPE_BUFFER);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
	 */
	public Context getContext()
	{
		return _context;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getContextPath()
	 */
	public String getContextPath()
	{
		return _contextPath;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getCookies()
	 */
	public Cookie[] getCookies()
	{
		if (_cookiesExtracted)
			return _cookies == null?null:_cookies.getCookies();

		_cookiesExtracted = true;

		Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER);

		// Handle no cookies
		if (enm != null)
		{
			if (_cookies == null)
				_cookies = new CookieCutter();

			while (enm.hasMoreElements())
			{
				String c = (String)enm.nextElement();
				_cookies.addCookieField(c);
			}
		}

		return _cookies == null?null:_cookies.getCookies();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
	 */
	public long getDateHeader(String name)
	{
		return _connection.getRequestFields().getDateField(name);
	}

	/* ------------------------------------------------------------ */
	public DispatcherType getDispatcherType()
	{
		return _dispatcherType;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
	 */
	public String getHeader(String name)
	{
		return _connection.getRequestFields().getStringField(name);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
	 */
	public Enumeration getHeaderNames()
	{
		return _connection.getRequestFields().getFieldNames();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
	 */
	public Enumeration getHeaders(String name)
	{
		Enumeration e = _connection.getRequestFields().getValues(name);
		if (e == null)
			return Collections.enumeration(Collections.EMPTY_LIST);
		return e;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the inputState.
	 */
	public int getInputState()
	{
		return _inputState;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getInputStream()
	 */
	public ServletInputStream getInputStream() throws IOException
	{
		if (_inputState != __NONE && _inputState != _STREAM)
			throw new IllegalStateException("READER");
		_inputState = _STREAM;
		return _connection.getInputStream();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
	 */
	public int getIntHeader(String name)
	{
		return (int)_connection.getRequestFields().getLongField(name);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getLocalAddr()
	 */
	public String getLocalAddr()
	{
		return _endp == null?null:_endp.getLocalAddr();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getLocale()
	 */
	public Locale getLocale()
	{
		Enumeration enm = _connection.getRequestFields().getValues( "Accept-Language", ", \t" );

		// handle no locale
		if (enm == null || !enm.hasMoreElements())
			return Locale.getDefault();

		// sort the list in quality order
		List acceptLanguage = HttpFields.qualityList(enm);
		if (acceptLanguage.size() == 0)
			return Locale.getDefault();

		int size = acceptLanguage.size();

		if (size > 0)
		{
			String language = (String)acceptLanguage.get(0);
			language = HttpFields.valueParameters(language,null);
			String country = "";
			int dash = language.indexOf('-');
			if (dash > -1)
			{
				country = language.substring(dash + 1).trim();
				language = language.substring(0,dash).trim();
			}
			return new Locale(language,country);
		}

		return Locale.getDefault();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getLocales()
	 */
	public Enumeration getLocales()
	{

		Enumeration enm = _connection.getRequestFields().getValues( "Accept-Language", ", \t" );

		// handle no locale
		if (enm == null || !enm.hasMoreElements())
			return Collections.enumeration(__defaultLocale);

		// sort the list in quality order
		List acceptLanguage = HttpFields.qualityList(enm);

		if (acceptLanguage.size() == 0)
			return Collections.enumeration(__defaultLocale);

		Object langs = null;
		int size = acceptLanguage.size();

		// convert to locals
		for (int i = 0; i < size; i++)
		{
			String language = (String)acceptLanguage.get(i);
			language = HttpFields.valueParameters(language,null);
			String country = "";
			int dash = language.indexOf('-');
			if (dash > -1)
			{
				country = language.substring(dash + 1).trim();
				language = language.substring(0,dash).trim();
			}
			langs = LazyList.ensureSize(langs,size);
			langs = LazyList.add(langs,new Locale(language,country));
		}

		if (LazyList.size(langs) == 0)
			return Collections.enumeration(__defaultLocale);

		return Collections.enumeration(LazyList.getList(langs));
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getLocalName()
	 */
	public String getLocalName()
	{
		if (_endp == null)
			return null;
		if (_dns)
			return _endp.getLocalHost();

		String local = _endp.getLocalAddr();
		if (local != null && local.indexOf(':') >= 0)
			local = "[" + local + "]";
		return local;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getLocalPort()
	 */
	public int getLocalPort()
	{
		return _endp == null?0:_endp.getLocalPort();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getMethod()
	 */
	public String getMethod()
	{
		return _method;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
	 */
	public String getParameter(String name)
	{
		if (!_paramsExtracted)
			extractParameters();
		return (String)_parameters.getValue(name,0);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getParameterMap()
	 */
	public Map getParameterMap()
	{
		if (!_paramsExtracted)
			extractParameters();

		return Collections.unmodifiableMap(_parameters.toStringArrayMap());
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getParameterNames()
	 */
	public Enumeration getParameterNames()
	{
		if (!_paramsExtracted)
			extractParameters();
		return Collections.enumeration(_parameters.keySet());
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the parameters.
	 */
	public MultiMap<String> getParameters()
	{
		return _parameters;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
	 */
	public String[] getParameterValues(String name)
	{
		if (!_paramsExtracted)
			extractParameters();
		List<Object> vals = _parameters.getValues(name);
		if (vals == null)
			return null;
		return vals.toArray(new String[vals.size()]);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getPathInfo()
	 */
	public String getPathInfo()
	{
		return _pathInfo;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
	 */
	public String getPathTranslated()
	{
		if (_pathInfo == null || _context == null)
			return null;
		return _context.getRealPath(_pathInfo);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getProtocol()
	 */
	public String getProtocol()
	{
		return _protocol;
	}

	/* ------------------------------------------------------------ */
	public String getQueryEncoding()
	{
		return _queryEncoding;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getQueryString()
	 */
	public String getQueryString()
	{
		if (_queryString == null && _uri != null)
		{
			if (_queryEncoding == null)
				_queryString = _uri.getQuery();
			else
				_queryString = _uri.getQuery(_queryEncoding);
		}
		return _queryString;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getReader()
	 */
	public BufferedReader getReader() throws IOException
	{
		if (_inputState != __NONE && _inputState != __READER)
			throw new IllegalStateException("STREAMED");

		if (_inputState == __READER)
			return _reader;

		String encoding = getCharacterEncoding();
		if (encoding == null)
			encoding = StringUtil.__ISO_8859_1;

		if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
		{
			final ServletInputStream in = getInputStream();
			_readerEncoding = encoding;
			_reader = new BufferedReader(new InputStreamReader(in,encoding))
			{
				@Override
				public void close() throws IOException
				{
					in.close();
				}
			};
		}
		_inputState = __READER;
		return _reader;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
	 */
	public String getRealPath(String path)
	{
		if (_context == null)
			return null;
		return _context.getRealPath(path);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getRemoteAddr()
	 */
	public String getRemoteAddr()
	{
		if (_remoteAddr != null)
			return _remoteAddr;
		return _endp == null?null:_endp.getRemoteAddr();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getRemoteHost()
	 */
	public String getRemoteHost()
	{
		if (_dns)
		{
			if (_remoteHost != null)
			{
				return _remoteHost;
			}
			return _endp == null?null:_endp.getRemoteHost();
		}
		return getRemoteAddr();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getRemotePort()
	 */
	public int getRemotePort()
	{
		return _endp == null?0:_endp.getRemotePort();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
	 */
	public String getRemoteUser()
	{
		Principal p = getUserPrincipal();
		if (p == null)
			return null;
		return p.getName();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
	 */
	public RequestDispatcher getRequestDispatcher(String path)
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
	 */
	public String getRequestedSessionId()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getRequestURI()
	 */
	public String getRequestURI()
	{
		if (_requestURI == null && _uri != null)
			_requestURI = _uri.getPathAndParam();
		return _requestURI;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getRequestURL()
	 */
	public StringBuffer getRequestURL()
	{
		final StringBuffer url = new StringBuffer(48);
		synchronized (url)
		{
			String scheme = getScheme();
			int port = getServerPort();

			url.append(scheme);
			url.append("://");
			url.append(getServerName());
			if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
			{
				url.append(':');
				url.append(_port);
			}

			url.append(getRequestURI());
			return url;
		}
	}

	/* ------------------------------------------------------------ */
	public Response getResponse()
	{
		return _connection._response;
	}

	/* ------------------------------------------------------------ */
	/**
	 * 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 StringBuilder getRootURL()
	{
		StringBuilder url = new StringBuilder(48);
		String scheme = getScheme();
		int port = getServerPort();

		url.append(scheme);
		url.append("://");
		url.append(getServerName());

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

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getScheme()
	 */
	public String getScheme()
	{
		return _scheme;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getServerName()
	 */
	public String getServerName()
	{
		// Return already determined host
		if (_serverName != null)
			return _serverName;

		if (_uri == null)
			throw new IllegalStateException("No uri");

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

		// Return host from header field
		Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER);
		if (hostPort != null)
		{
			loop: for (int i = hostPort.putIndex(); i-- > hostPort.getIndex();)
			{
				char ch = (char)(0xff & hostPort.peek(i));
				switch (ch)
				{
					case ']':
						break loop;

					case ':':
						_serverName = BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(),i - hostPort.getIndex()));
						try
						{
							_port = BufferUtil.toInt(hostPort.peek(i + 1,hostPort.putIndex() - i - 1));
						}
						catch (NumberFormatException e)
						{
							try
							{
								if (_connection != null)
									_connection._generator.sendError(HttpStatus.BAD_REQUEST_400,"Bad Host header",null,true);
							}
							catch (IOException e1)
							{
								throw new RuntimeException(e1);
							}
						}
						return _serverName;
				}
			}
			if (_serverName == null || _port < 0)
			{
				_serverName = BufferUtil.to8859_1_String(hostPort);
				_port = 0;
			}

			return _serverName;
		}

		// Return host from connection
		if (_connection != null)
		{
			_serverName = getLocalName();
			_port = getLocalPort();
			if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
				return _serverName;
		}

		// Return the local host
		try
		{
			_serverName = InetAddress.getLocalHost().getHostAddress();
		}
		catch (java.net.UnknownHostException e)
		{
			LOG.trace("",e);
		}
		return _serverName;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getServerPort()
	 */
	public int getServerPort()
	{
		if (_port <= 0)
		{
			if (_serverName == null)
				getServerName();

			if (_port <= 0)
			{
				if (_serverName != null && _uri != null)
					_port = _uri.getPort();
				else
					_port = _endp == null?0:_endp.getLocalPort();
			}
		}

		if (_port <= 0)
		{
			if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
				return 443;
			return 80;
		}
		return _port;
	}

	/* ------------------------------------------------------------ */
	public ServletContext getServletContext()
	{
		return _context;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getServletPath()
	 */
	public String getServletPath()
	{
		if (_servletPath == null)
			_servletPath = "";
		return _servletPath;
	}

	/* ------------------------------------------------------------ */
	public ServletResponse getServletResponse()
	{
		return _connection.getResponse();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getSession()
	 */
	public HttpSession getSession()
	{
		return getSession(true);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
	 */
	public HttpSession getSession(boolean create)
	{
		throw new UnsupportedOperationException();
	}

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

	/* ------------------------------------------------------------ */
	/**
	 * Get Request TimeStamp
	 *
	 * @return The time that the request was received.
	 */
	public Buffer getTimeStampBuffer()
	{
		if (_timeStampBuffer == null && _timeStamp > 0)
			_timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp);
		return _timeStampBuffer;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the uri.
	 */
	public HttpURI getUri()
	{
		return _uri;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
	 */
	public Principal getUserPrincipal()
	{
		return null;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get timestamp of the request dispatch
	 *
	 * @return timestamp
	 */
	public long getDispatchTime()
	{
		return _dispatchTime;
	}

	/* ------------------------------------------------------------ */
	public boolean isHandled()
	{
		return _handled;
	}

	public boolean isAsyncStarted()
	{
	   return _async.isAsyncStarted();
	}


	/* ------------------------------------------------------------ */
	public boolean isAsyncSupported()
	{
		return _asyncSupported;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
	 */
	public boolean isRequestedSessionIdFromCookie()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
	 */
	public boolean isRequestedSessionIdFromUrl()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
	 */
	public boolean isRequestedSessionIdFromURL()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
	 */
	public boolean isRequestedSessionIdValid()
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#isSecure()
	 */
	public boolean isSecure()
	{
		return _connection.isConfidential(this);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
	 */
	public boolean isUserInRole(String role)
	{
		return false;
	}

	/* ------------------------------------------------------------ */
	public HttpSession recoverNewSession(Object key)
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	protected void recycle()
	{
		if (_inputState == __READER)
		{
			try
			{
				int r = _reader.read();
				while (r != -1)
					r = _reader.read();
			}
			catch (Exception e)
			{
				LOG.trace("",e);
				_reader = null;
			}
		}

		_async.recycle();
		_asyncSupported = true;
		_handled = false;
		if (_context != null)
			throw new IllegalStateException("Request in context!");
		if (_attributes != null)
			_attributes.clearAttributes();
		_characterEncoding = null;
		_contextPath = null;
		if (_cookies != null)
			_cookies.reset();
		_cookiesExtracted = false;
		_context = null;
		_serverName = null;
		_method = null;
		_pathInfo = null;
		_port = 0;
		_protocol = HttpVersions.HTTP_1_1;
		_queryEncoding = null;
		_queryString = null;
		_requestURI = null;
		_scheme = URIUtil.HTTP;
		_servletPath = null;
		_timeStamp = 0;
		_timeStampBuffer = null;
		_uri = null;
		if (_baseParameters != null)
			_baseParameters.clear();
		_parameters = null;
		_paramsExtracted = false;
		_inputState = __NONE;

		_multiPartInputStream = null;
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
	 */
	public void removeAttribute(String name)
	{
		Object old_value = _attributes == null?null:_attributes.getAttribute(name);

		if (_attributes != null)
			_attributes.removeAttribute(name);

		if (old_value != null)
		{
			if (_requestAttributeListeners != null)
			{
				final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
				final int size = LazyList.size(_requestAttributeListeners);
				for (int i = 0; i < size; i++)
				{
					final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
					if (listener instanceof ServletRequestAttributeListener)
					{
						final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
						l.attributeRemoved(event);
					}
				}
			}
		}
	}

	/* ------------------------------------------------------------ */
	public void removeEventListener(final EventListener listener)
	{
		_requestAttributeListeners = LazyList.remove(_requestAttributeListeners,listener);
	}

	/* ------------------------------------------------------------ */
	public void setAsyncSupported(boolean supported)
	{
		_asyncSupported = supported;
	}

	/* ------------------------------------------------------------ */
	/*
	 * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
	 * {@link #setQueryEncoding}. <p> if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then the response buffer is flushed with @{link
	 * #flushResponseBuffer} <p> if the attribute name is "org.eclipse.jetty.io.EndPoint.maxIdleTime", then the value is passed to the associated {@link
	 * EndPoint#setMaxIdleTime}.
	 *
	 * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
	 */
	public void setAttribute(String name, Object value)
	{
		Object old_value = _attributes == null?null:_attributes.getAttribute(name);

		if (name.startsWith("org.eclipse.jetty."))
		{
			if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
				setQueryEncoding(value == null?null:value.toString());
			else if ("org.eclipse.jetty.server.sendContent".equals(name))
			{
				try
				{
					((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value);
				}
				catch (IOException e)
				{
					throw new RuntimeException(e);
				}
			}
			else if ("org.eclipse.jetty.server.ResponseBuffer".equals(name))
			{
				try
				{
					final ByteBuffer byteBuffer = (ByteBuffer)value;
					synchronized (byteBuffer)
					{
						NIOBuffer buffer = byteBuffer.isDirect()?new DirectNIOBuffer(byteBuffer,true):new IndirectNIOBuffer(byteBuffer,true);
						((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer);
					}
				}
				catch (IOException e)
				{
					throw new RuntimeException(e);
				}
			}
			else if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
			{
				try
				{
					getConnection().getEndPoint().setMaxIdleTime(Integer.valueOf(value.toString()));
				}
				catch (IOException e)
				{
					throw new RuntimeException(e);
				}
			}
		}

		if (_attributes == null)
			_attributes = new AttributesMap();
		_attributes.setAttribute(name,value);

		if (_requestAttributeListeners != null)
		{
			final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
			final int size = LazyList.size(_requestAttributeListeners);
			for (int i = 0; i < size; i++)
			{
				final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
				if (listener instanceof ServletRequestAttributeListener)
				{
					final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;

					if (old_value == null)
						l.attributeAdded(event);
					else if (value == null)
						l.attributeRemoved(event);
					else
						l.attributeReplaced(event);
				}
			}
		}
	}

	/* ------------------------------------------------------------ */
	/*
	 */
	public void setAttributes(Attributes attributes)
	{
		_attributes = attributes;
	}

	/* ------------------------------------------------------------ */

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
	 */
	public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
	{
		if (_inputState != __NONE)
			return;

		_characterEncoding = encoding;

		// check encoding is supported
		if (!StringUtil.isUTF8(encoding))
			// noinspection ResultOfMethodCallIgnored
			"".getBytes(encoding);
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
	 */
	public void setCharacterEncodingUnchecked(String encoding)
	{
		_characterEncoding = encoding;
	}

	/* ------------------------------------------------------------ */
	// final so we can safely call this from constructor
	protected final void setConnection(AbstractHttpConnection connection)
	{
		_connection = connection;
		_async.setConnection(connection);
		_endp = connection.getEndPoint();
		_dns = connection.getResolveNames();
	}

	/* ------------------------------------------------------------ */
	/*
	 * @see javax.servlet.ServletRequest#getContentType()
	 */
	public void setContentType(String contentType)
	{
		_connection.getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,contentType);

	}

	/* ------------------------------------------------------------ */
	/**
	 * Set request context
	 *
	 * @param context
	 *            context object
	 */
	public void setContext(Context context)
	{
		_newContext = _context != context;
		_context = context;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return True if this is the first call of {@link #takeNewContext()} since the last
	 *         {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
	 */
	public boolean takeNewContext()
	{
		boolean nc = _newContext;
		_newContext = false;
		return nc;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Sets the "context path" for this request
	 *
	 * @see HttpServletRequest#getContextPath()
	 */
	public void setContextPath(String contextPath)
	{
		_contextPath = contextPath;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param cookies
	 *            The cookies to set.
	 */
	public void setCookies(Cookie[] cookies)
	{
		if (_cookies == null)
			_cookies = new CookieCutter();
		_cookies.setCookies(cookies);
	}

	/* ------------------------------------------------------------ */
	public void setDispatcherType(DispatcherType type)
	{
		_dispatcherType = type;
	}

	/* ------------------------------------------------------------ */
	public void setHandled(boolean h)
	{
		_handled = h;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param method
	 *            The method to set.
	 */
	public void setMethod(String method)
	{
		_method = method;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param parameters
	 *            The parameters to set.
	 */
	public void setParameters(MultiMap<String> parameters)
	{
		_parameters = (parameters == null)?_baseParameters:parameters;
		if (_paramsExtracted && _parameters == null)
			throw new IllegalStateException();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param pathInfo
	 *            The pathInfo to set.
	 */
	public void setPathInfo(String pathInfo)
	{
		_pathInfo = pathInfo;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param protocol
	 *            The protocol to set.
	 */
	public void setProtocol(String protocol)
	{
		_protocol = protocol;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
	 * geParameter methods.
	 *
	 * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
	 *
	 * @param queryEncoding
	 */
	public void setQueryEncoding(String queryEncoding)
	{
		_queryEncoding = queryEncoding;
		_queryString = null;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param queryString
	 *            The queryString to set.
	 */
	public void setQueryString(String queryString)
	{
		_queryString = queryString;
		_queryEncoding = null; //assume utf-8
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param addr
	 *            The address to set.
	 */
	public void setRemoteAddr(String addr)
	{
		_remoteAddr = addr;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param host
	 *            The host to set.
	 */
	public void setRemoteHost(String host)
	{
		_remoteHost = host;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param requestURI
	 *            The requestURI to set.
	 */
	public void setRequestURI(String requestURI)
	{
		_requestURI = requestURI;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param scheme
	 *            The scheme to set.
	 */
	public void setScheme(String scheme)
	{
		_scheme = scheme;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param host
	 *            The host to set.
	 */
	public void setServerName(String host)
	{
		_serverName = host;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param port
	 *            The port to set.
	 */
	public void setServerPort(int port)
	{
		_port = port;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param servletPath
	 *            The servletPath to set.
	 */
	public void setServletPath(String servletPath)
	{
		_servletPath = servletPath;
	}

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

	/* ------------------------------------------------------------ */
	/**
	 * @param uri
	 *            The uri to set.
	 */
	public void setUri(HttpURI uri)
	{
		_uri = uri;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set timetstamp of request dispatch
	 *
	 * @param value
	 *            timestamp
	 */
	public void setDispatchTime(long value)
	{
		_dispatchTime = value;
	}

	/* ------------------------------------------------------------ */
	public AsyncContext startAsync() throws IllegalStateException
	{
		if (!_asyncSupported)
			throw new IllegalStateException("!asyncSupported");
		_async.startAsync();
		return _async;
	}

	/* ------------------------------------------------------------ */
	public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
	{
		if (!_asyncSupported)
			throw new IllegalStateException("!asyncSupported");
		_async.startAsync(_context,servletRequest,servletResponse);
		return _async;
	}

	/* ------------------------------------------------------------ */
	@Override
	public String toString()
	{
		return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
	}

	/* ------------------------------------------------------------ */
	public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	public Part getPart(String name) throws IOException, ServletException
	{                
		getParts();
		return _multiPartInputStream.getPart(name);
	}

	/* ------------------------------------------------------------ */
	public Collection<Part> getParts() throws IOException, ServletException
	{
		if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
			throw new ServletException("Content-Type != multipart/form-data");
		
		if (_multiPartInputStream == null)
			_multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM);
		
		if (_multiPartInputStream == null)
		{
			MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
			
			if (config == null)
				throw new IllegalStateException("No multipart config for servlet");
			
			_multiPartInputStream = new MultiPartInputStream(getInputStream(), 
															 getContentType(), config, 
															 (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
			
			setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
			setAttribute(__MULTIPART_CONTEXT, _context);
			Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing 
			for (Part p:parts)
			{
				MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
				if (mp.getContentDispositionFilename() == null)
				{
					//Servlet Spec 3.0 pg 23, parts without filenames must be put into init params
					String charset = null;
					if (mp.getContentType() != null)
						charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType()));

					ByteArrayOutputStream os = null;
					InputStream is = mp.getInputStream(); //get the bytes regardless of being in memory or in temp file
					try
					{
						os = new ByteArrayOutputStream();
						IO.copy(is, os);
						String content=new String(os.toByteArray(),charset==null?StringUtil.__UTF8:charset);   
						getParameter(""); //cause params to be evaluated
						getParameters().add(mp.getName(), content);
					}
					finally
					{
						IO.close(os);
						IO.close(is);
					}
				}
			}
		}

		return _multiPartInputStream.getParts();
	}

	/* ------------------------------------------------------------ */
	public void login(String username, String password) throws ServletException
	{
		throw new UnsupportedOperationException();
	}

	/* ------------------------------------------------------------ */
	public void logout() throws ServletException
	{
		throw new UnsupportedOperationException();
	}
	
	/* ------------------------------------------------------------ */
	/**
	 * Merge in a new query string. The query string is merged with the existing parameters and {@link #setParameters(MultiMap)} and
	 * {@link #setQueryString(String)} are called with the result. The merge is according to the rules of the servlet dispatch forward method.
	 *
	 * @param query
	 *            The query string to merge into the request.
	 */
	public void mergeQueryString(String query)
	{
		// extract parameters from dispatch query
		MultiMap<String> parameters = new MultiMap<String>();
		UrlEncoded.decodeTo(query,parameters, StringUtil.__UTF8); //have to assume UTF-8 because we can't know otherwise

		boolean merge_old_query = false;

		// Have we evaluated parameters
		if (!_paramsExtracted)
			extractParameters();

		// Are there any existing parameters?
		if (_parameters != null && _parameters.size() > 0)
		{
			// Merge parameters; new parameters of the same name take precedence.
			Iterator<Entry<String, Object>> iter = _parameters.entrySet().iterator();
			while (iter.hasNext())
			{
				Map.Entry<String, Object> entry = iter.next();
				String name = entry.getKey();

				// If the names match, we will need to remake the query string
				if (parameters.containsKey(name))
					merge_old_query = true;

				// Add the old values to the new parameter map
				Object values = entry.getValue();
				for (int i = 0; i < LazyList.size(values); i++)
					parameters.add(name,LazyList.get(values,i));
			}
		}

		if (_queryString != null && _queryString.length() > 0)
		{
			if (merge_old_query)
			{
				StringBuilder overridden_query_string = new StringBuilder();
				MultiMap<String> overridden_old_query = new MultiMap<String>();
				UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding());//decode using any queryencoding set for the request
				
				
				MultiMap<String> overridden_new_query = new MultiMap<String>();
				UrlEncoded.decodeTo(query,overridden_new_query,StringUtil.__UTF8); //have to assume utf8 as we cannot know otherwise

				Iterator<Entry<String, Object>> iter = overridden_old_query.entrySet().iterator();
				while (iter.hasNext())
				{
					Map.Entry<String, Object> entry = iter.next();
					String name = entry.getKey();
					if (!overridden_new_query.containsKey(name))
					{
						Object values = entry.getValue();
						for (int i = 0; i < LazyList.size(values); i++)
						{
							overridden_query_string.append("&").append(name).append("=").append((Object)LazyList.get(values,i));
						}
					}
				}

				query = query + overridden_query_string;
			}
			else
			{
				query = query + "&" + _queryString;
			}
		}

		setParameters(parameters);
		setQueryString(query);
	}
}