You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2009/11/26 07:41:01 UTC

svn commit: r884412 [3/4] - /tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java?rev=884412&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java Thu Nov 26 06:41:00 2009
@@ -0,0 +1,980 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.lite.http.MultiMap.Entry;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.Hex;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOReader;
+import org.apache.tomcat.lite.io.IOWriter;
+import org.apache.tomcat.lite.io.SocketIOChannel;
+import org.apache.tomcat.lite.io.UrlEncoding;
+
+public class HttpRequest extends HttpMessage {
+    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+
+    protected CBuffer schemeMB;    
+    protected CBuffer methodMB;
+    protected CBuffer remoteAddrMB;
+    protected CBuffer remoteHostMB;
+    protected int remotePort;
+    
+    protected CBuffer localNameMB;
+    protected CBuffer localAddrMB;
+    protected int localPort = -1;
+
+    // Host: header, or default:80
+    protected CBuffer serverNameMB;
+    protected int serverPort = -1;
+    
+    
+    // ==== Derived fields, computed after request is received ===
+    
+    protected CBuffer requestURI;
+    protected CBuffer queryMB;
+    
+    protected BBuffer decodedUri = BBuffer.allocate();
+    protected CBuffer decodedUriMB;
+    
+    // Decoded query
+    protected MultiMap parameters;
+    
+    boolean parametersParsed = false;
+
+    protected IOWriter charEncoder = new IOWriter(null);
+    protected IOReader charDecoder = new IOReader(null);
+    protected UrlEncoding urlEncoding = new UrlEncoding();
+
+    // Reference to 'real' request object
+    // will not be recycled
+    public Object nativeRequest;
+    public Object wrapperRequest;
+    
+    boolean ssl = false;
+    
+    boolean async = false;
+    
+    private Map<String, Object> attributes = new HashMap<String, Object>();
+
+    /**
+     * Mapping data.
+     */
+    protected MappingData mappingData = new MappingData();
+
+
+    HttpRequest(HttpChannel httpCh) {
+        super(httpCh);
+        decodedUriMB = CBuffer.newInstance();
+        requestURI = CBuffer.newInstance();
+        queryMB = CBuffer.newInstance();
+        serverNameMB = CBuffer.newInstance();
+        
+        parameters = new MultiMap();
+        
+        schemeMB = 
+            CBuffer.newInstance();
+        methodMB = CBuffer.newInstance();
+        initRemote();
+    }
+   
+    protected void initRemote() {
+        remoteAddrMB = CBuffer.newInstance();
+        localNameMB = CBuffer.newInstance();
+        remoteHostMB = CBuffer.newInstance();
+        localAddrMB = CBuffer.newInstance();
+    }
+    
+    public void recycle() {
+        super.recycle();
+        schemeMB.recycle();
+        methodMB.set("GET");
+        requestURI.recycle();
+        queryMB.recycle();
+        decodedUriMB.recycle();
+        
+        parameters.recycle();
+        remoteAddrMB.recycle();
+        remoteHostMB.recycle();
+        parametersParsed = false;
+        ssl = false;
+        async = false;
+        asyncTimeout = -1;
+        charEncoder.recycle();
+        
+        localPort = -1;
+        remotePort = -1;
+        localAddrMB.recycle();
+        localNameMB.recycle();
+        
+        serverPort = -1;
+        serverNameMB.recycle();
+        decodedUri.recycle();
+        decodedQuery.recycle();
+    }
+    
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public void setAttribute(String name, Object o) {
+        if (o == null) {
+            attributes.remove(name);
+        } else {
+            attributes.put(name, o);
+        }
+    }
+    // getAttributeNames not supported
+    
+    public Map<String, Object> attributes() {
+        return attributes;
+    }
+    
+    
+    
+    public CBuffer method() {
+        return methodMB;
+    }
+    
+    public String getMethod() {
+        return methodMB.toString();
+    }
+    
+    public void setMethod(String method) {
+        methodMB.set(method);
+    }
+    
+    public CBuffer scheme() {
+        return schemeMB;
+    }
+    
+    public String getScheme() {
+        String scheme = schemeMB.toString();
+        if (scheme == null) {
+            return "http";
+        }
+        return scheme;
+    }
+    
+    public void setScheme(String s) {
+        schemeMB.set(s);
+    }
+    
+    public MappingData getMappingData() {
+        return (mappingData);
+    }
+    
+    /** 
+     * Parse query parameters - but not POST body. 
+     * 
+     * If you don't call this method, getParameters() will 
+     * also read the body for POST with x-www-url-encoded 
+     * mime type. 
+     */
+    public void parseQueryParameters() {
+        parseQuery();
+    }
+
+    /**
+     * Explicitely parse the body, adding the parameters to 
+     * those from the query ( if already parsed ).
+     * 
+     * By default servlet mode ( both query and body ) is used.
+     */
+    public void parsePostParameters() {
+        parseBody();
+    }
+    
+    MultiMap getParameters() {
+        if (!parametersParsed) {
+            parseQuery();
+            parseBody();
+        }
+        return parameters;
+    }
+    
+    public Enumeration<String> getParameterNames() {
+        return getParameters().names();
+    }
+    
+    /** 
+     * Expensive, creates a copy on each call. 
+     * @param name
+     * @return
+     */
+    public String[] getParameterValues(String name) {
+        Entry entry = getParameters().getEntry(name);
+        if (entry == null) {
+            return null;
+        }
+        String[] values = new String[entry.values.size()];
+        for (int j = 0; j < values.length; j++) {
+            values[j] = entry.values.get(j).toString();
+        }
+        return values;
+    }
+    
+    // Inefficient - we convert from a different representation.
+    public Map<String, String[]> getParameterMap() {
+        // we could allow 'locking' - I don't think this is 
+        // a very useful optimization
+        Map<String, String[]> map = new HashMap();
+        for (int i = 0; i < getParameters().size(); i++) {
+            Entry entry = getParameters().getEntry(i);
+            if (entry == null) {
+                continue;
+            }
+            if (entry.key == null) {
+                continue;
+            }
+            String name = entry.key.toString();
+            String[] values = new String[entry.values.size()];
+            for (int j = 0; j < values.length; j++) {
+                values[j] = entry.values.get(j).toString();
+            }
+            map.put(name, values);
+        }
+        return map;
+    }
+    
+    public String getParameter(String name) {
+        CharSequence value = getParameters().get(name);
+        if (value == null) {
+            return null;
+        }
+        return value.toString();
+    }
+    
+    public void setParameter(String name, String value) {
+        getParameters().set(name, value);
+    }
+    
+    public void addParameter(String name, String values) {
+        getParameters().add(name, values);
+    }
+
+    public CBuffer queryString() {
+        return queryMB;
+    }
+
+    // TODO
+    void serializeParameters(Appendable cc) throws IOException {
+        int keys = parameters.size();
+        boolean notFirst = false;
+        for (int i = 0; i < parameters.size(); i++) {
+            Entry entry = parameters.getEntry(i);
+            for (int j = 0; j < entry.values.size(); j++) {
+                // TODO: Uencode
+                if (notFirst) {
+                    cc.append('&');
+                } else {
+                    notFirst = true;
+                }
+                cc.append(entry.key);
+                cc.append("=");
+                cc.append(entry.values.get(j).getValue());
+            }
+        }
+    }
+    
+    public void setURI(CharSequence encoded) {
+        decodedUriMB.recycle();
+        decodedUriMB.append(encoded);
+        // TODO: generate % encoding ( reverse of decodeRequest )
+    }
+    
+    public CBuffer decodedURI() {
+        return decodedUriMB;
+    }
+
+    public CBuffer requestURI() {
+        return requestURI;
+    }
+    
+    /** 
+     * Not decoded - %xx as in original.
+     * @return
+     */
+    public String getRequestURI() {
+        return requestURI.toString();
+    }
+
+    public void setRequestURI(String encodedUri) {
+        requestURI.set(encodedUri);
+    }
+
+    CBuffer getOrAdd(String name) {
+        CBuffer header = getMimeHeaders().getHeader(name);
+        if (header == null) {
+            header = getMimeHeaders().addValue(name);
+        }
+        return header;
+    }
+
+    /** 
+     * Set the Host header of the request.
+     * @param target
+     */
+    public void setHost(String target) {
+        serverNameMB.recycle();
+        getOrAdd("Host").set(target);
+    }
+    
+    // XXX
+    public CBuffer serverName() {
+        if (serverNameMB.length() == 0) {
+            parseHost();
+        }
+        return serverNameMB;
+    }
+
+    public String getServerName() {
+        return serverName().toString();
+    }
+    
+    public void setServerName(String name)  {
+        serverName().set(name);
+    }
+    
+    public int getServerPort() {
+        serverName();
+        return serverPort;
+    }
+    
+    public void setServerPort(int serverPort ) {
+        this.serverPort=serverPort;
+    }
+
+    public CBuffer remoteAddr() {
+        if (remoteAddrMB.length() == 0) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            if (iochannel instanceof SocketIOChannel) {
+                SocketIOChannel channel = (SocketIOChannel) iochannel;
+
+                String addr = (channel == null) ?
+                        "127.0.0.1" : 
+                            channel.getAddress(true).getHostAddress();
+
+                remoteAddrMB.set(addr);
+            }
+        }
+        return remoteAddrMB;
+    }
+
+    public CBuffer remoteHost() {
+        if (remoteHostMB.length() == 0) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            if (iochannel instanceof SocketIOChannel) {
+                SocketIOChannel channel = (SocketIOChannel) iochannel;
+                String addr = (channel == null) ?
+                        "127.0.0.1" : 
+                            channel.getAddress(true).getCanonicalHostName();
+
+                remoteHostMB.set(addr);
+            }
+        }
+        return remoteHostMB;
+    }
+
+    public CBuffer localName() {
+        return localNameMB;
+    }    
+
+    public CBuffer localAddr() {
+        return localAddrMB;
+    }
+    
+    public int getRemotePort(){
+        if (remotePort == -1) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            if (iochannel instanceof SocketIOChannel) {
+                SocketIOChannel channel = (SocketIOChannel) iochannel;
+                remotePort = (channel == null) ?
+                            0 : channel.getPort(true);
+            }
+        }
+        return remotePort;
+    }
+        
+    public void setRemotePort(int port){
+        this.remotePort = port;
+    }
+    
+    public int getLocalPort(){
+        if (localPort == -1) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            if (iochannel instanceof SocketIOChannel) {
+                SocketIOChannel channel = (SocketIOChannel) iochannel;
+                localPort = (channel == null) ?
+                            0 : channel.getPort(false);
+            }
+        }
+        return localPort;
+    }
+        
+    public void setLocalPort(int port){
+        this.localPort = port;
+    }
+    
+    public void sendHead() throws IOException {
+        httpCh.sendRequestHeaders(httpCh);
+    }
+
+    /** 
+     * Convert the request to bytes, ready to send.
+     */
+    public void serialize(IOBuffer rawSendBuffers2) throws IOException {
+        rawSendBuffers2.append(method());
+        rawSendBuffers2.append(BBuffer.SP);
+
+        // TODO: encode or use decoded
+        rawSendBuffers2.append(requestURI());
+        if (queryString().length() > 0) {
+            rawSendBuffers2.append("?");
+            rawSendBuffers2.append(queryString());
+        }
+
+        rawSendBuffers2.append(BBuffer.SP);
+        rawSendBuffers2.append(protocol());
+        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+        
+        super.serializeHeaders(rawSendBuffers2);
+    }
+
+    /**
+     * Parse host.
+     * @param serverNameMB2 
+     * @throws IOException 
+     */
+    boolean parseHost()  {
+        MultiMap.Entry hostHF = getMimeHeaders().getEntry("Host");
+        if (hostHF == null) {
+            // HTTP/1.0
+            // Default is what the socket tells us. Overriden if a host is
+            // found/parsed
+            return true;
+        }
+
+        BBuffer valueBC = hostHF.valueB;
+        byte[] valueB = valueBC.array();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+        int colonPos = -1;
+        
+        serverNameMB.recycle();
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+            serverNameMB.append(b);
+            if (b == ']') {
+                bracketClosed = true;
+            } 
+        }
+
+        if (colonPos < 0) {
+            if (!ssl) {
+                // 80 - Default HTTP port
+                setServerPort(80);
+            } else {
+                // 443 - Default HTTPS port
+                setServerPort(443);
+            }
+        } else {
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = Hex.DEC[(int) valueB[i + valueS]];
+                if (charValue == -1) {
+                    // we don't return 400 - could do it
+                    return false;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            setServerPort(port);
+
+        }
+        return true;
+    }
+    
+    // TODO: this is from coyote - MUST be rewritten !!!
+    // - cleaner
+    // - chunked encoding for body
+    // - buffer should be in a pool, etc.
+    /**
+     * Post data buffer.
+     */
+    public final static int CACHED_POST_LEN = 8192;
+    
+    public  byte[] postData = null;
+
+    private long asyncTimeout = -1;
+    
+    /**
+     * Parse request parameters.
+     */
+    protected void parseQuery() {
+
+        parametersParsed = true;
+
+        // getCharacterEncoding() may have been overridden to search for
+        // hidden form field containing request encoding
+        String enc = getEncoding();
+
+//        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
+//        if (enc != null) {
+//            parameters.setEncoding(enc);
+////            if (useBodyEncodingForURI) {
+////                parameters.setQueryStringEncoding(enc);
+////            }
+//        } else {
+//            parameters.setEncoding(DEFAULT_CHARACTER_ENCODING);
+////            if (useBodyEncodingForURI) {
+////                parameters.setQueryStringEncoding
+////                    (DEFAULT_CHARACTER_ENCODING);
+////            }
+//        }
+
+        handleQueryParameters();
+    }
+
+    // Copy - will be modified by decoding
+    BBuffer decodedQuery = BBuffer.allocate(1024);
+    
+    CBuffer tmpNameC = CBuffer.newInstance();
+    BBuffer tmpName = BBuffer.wrapper();
+    BBuffer tmpValue = BBuffer.wrapper();
+
+    CBuffer tmpNameCB = CBuffer.newInstance();
+    CBuffer tmpValueCB = CBuffer.newInstance();
+
+    /** 
+     * Process the query string into parameters
+     */
+    public void handleQueryParameters() {
+        if( queryMB.length() == 0) {
+            return;
+        }
+        
+        decodedQuery.recycle();
+        decodedQuery.append(getMsgBytes().query());
+        // TODO: option 'useBodyEncodingForUri' - versus UTF or ASCII
+        String queryStringEncoding = getEncoding();
+        processParameters( decodedQuery, queryStringEncoding );
+    }
+    
+    public void processParameters( BBuffer bc, String encoding ) {
+        if( bc.isNull()) 
+            return;
+        if (bc.remaining() ==0) {
+            return;
+        }
+        processParameters( bc.array(), bc.getOffset(),
+                           bc.getLength(), encoding);
+    }
+    
+    public void processParameters( byte bytes[], int start, int len, 
+            String enc ) {
+        int end=start+len;
+        int pos=start;
+
+        do {
+            boolean noEq=false;
+            int valStart=-1;
+            int valEnd=-1;
+
+            int nameStart=pos;
+            int nameEnd=BBuffer.indexOf(bytes, nameStart, end, '=' );
+            // Workaround for a&b&c encoding
+            int nameEnd2=BBuffer.indexOf(bytes, nameStart, end, '&' );
+            if( (nameEnd2!=-1 ) &&
+                    ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+                nameEnd=nameEnd2;
+                noEq=true;
+                valStart=nameEnd;
+                valEnd=nameEnd;
+            }
+            if( nameEnd== -1 ) 
+                nameEnd=end;
+
+            if( ! noEq ) {
+                valStart= (nameEnd < end) ? nameEnd+1 : end;
+                valEnd=BBuffer.indexOf(bytes, valStart, end, '&');
+                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+            }
+
+            pos=valEnd+1;
+
+            if( nameEnd<=nameStart ) {
+                // No name eg ...&=xx&... will trigger this
+                continue;
+            }
+            
+            // TODO: use CBuffer, recycle
+            tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
+            tmpValue.setBytes( bytes, valStart, valEnd-valStart );
+            
+            try {
+                parameters.add(urlDecode(tmpName, enc), 
+                        urlDecode(tmpValue, enc));
+            } catch (IOException e) {
+                // ignored
+            }
+        } while( pos<end );
+    }
+
+//    public void processParameters(char bytes[], int start, int len, 
+//            String enc ) {
+//        int end=start+len;
+//        int pos=start;
+//
+//        do {
+//            boolean noEq=false;
+//            int valStart=-1;
+//            int valEnd=-1;
+//
+//            int nameStart=pos;
+//            int nameEnd=CBuffer.indexOf(bytes, nameStart, end, '=' );
+//            // Workaround for a&b&c encoding
+//            int nameEnd2=CBuffer.indexOf(bytes, nameStart, end, '&' );
+//            if( (nameEnd2!=-1 ) &&
+//                    ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+//                nameEnd=nameEnd2;
+//                noEq=true;
+//                valStart=nameEnd;
+//                valEnd=nameEnd;
+//            }
+//            if( nameEnd== -1 ) 
+//                nameEnd=end;
+//
+//            if( ! noEq ) {
+//                valStart= (nameEnd < end) ? nameEnd+1 : end;
+//                valEnd=CBuffer.indexOf(bytes, valStart, end, '&');
+//                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+//            }
+//
+//            pos=valEnd+1;
+//
+//            if( nameEnd<=nameStart ) {
+//                // No name eg ...&=xx&... will trigger this
+//                continue;
+//            }
+//            
+//            // TODO: use CBuffer, recycle
+//            tmpNameCB.recycle();
+//            tmpValueCB.recycle();
+//            
+//            tmpNameCB.wrap( bytes, nameStart, nameEnd );
+//            tmpValueCB.wrap( bytes, valStart, valEnd );
+//            
+//            //CharChunk name = new CharChunk();
+//            //CharChunk value = new CharChunk();
+//            // TODO:
+//            try {
+//                parameters.add(urlDecode(tmpName, enc), 
+//                        urlDecode(tmpValue, enc));
+//            } catch (IOException e) {
+//                // ignored
+//            }
+//        } while( pos<end );
+//    }
+    
+    private String urlDecode(BBuffer bc, String enc)
+            throws IOException {
+        // Replace %xx
+        urlDecoder.urlDecode(bc, true);
+        
+        String result = null;
+        if (enc != null) {
+            result = bc.toString(enc);
+        } else {
+            // Ascii
+            
+            CBuffer cc = tmpNameC;
+            cc.recycle();
+            int length = bc.getLength();
+            byte[] bbuf = bc.array();
+            int start = bc.getStart();
+            cc.appendAscii(bbuf, start, length);
+            result = cc.toString();
+            cc.recycle();
+        }
+        return result;
+    }
+
+    private void processParameters( byte bytes[], int start, int len ) {
+        processParameters(bytes, start, len, getEncoding());
+    }
+    
+    protected void parseBody() {
+
+        parametersParsed = true;
+        String enc = getCharacterEncoding();
+
+//      if (usingInputStream || usingReader)
+//      return;
+        if (!getMethod().equalsIgnoreCase("POST"))
+            return;
+        
+        String contentType = getContentType();
+        if (contentType == null)
+            contentType = "";
+        int semicolon = contentType.indexOf(';');
+        if (semicolon >= 0) {
+            contentType = contentType.substring(0, semicolon).trim();
+        } else {
+            contentType = contentType.trim();
+        }
+        if (!("application/x-www-form-urlencoded".equals(contentType)))
+            return;
+
+        int len = getContentLength();
+
+        if (len > 0) {
+            try {
+                byte[] formData = null;
+                if (len < CACHED_POST_LEN) {
+                    if (postData == null)
+                        postData = new byte[CACHED_POST_LEN];
+                    formData = postData;
+                } else {
+                    formData = new byte[len];
+                }
+                int actualLen = readPostBody(formData, len);
+                if (actualLen == len) {
+                    processParameters(formData, 0, len);
+                }
+            } catch (Throwable t) {
+                ; // Ignore
+            }
+        }
+
+    }
+
+    /**
+     * Read post body in an array.
+     */
+    protected int readPostBody(byte body[], int len)
+        throws IOException {
+
+        int offset = 0;
+        do {
+            int inputLen = getBodyInputStream().read(body, offset, len - offset);
+            if (inputLen <= 0) {
+                return offset;
+            }
+            offset += inputLen;
+        } while ((len - offset) > 0);
+        return len;
+
+    }
+    
+    // Async support - a subset of servlet spec, the fancy stuff is in the 
+    // facade.
+    
+    public boolean isAsyncStarted() {
+        return async;
+    }
+
+    public void async() {
+        this.async = true;
+    }
+
+    public void setAsyncTimeout(long timeout) {
+        this.asyncTimeout  = timeout;
+    }
+    
+    /** 
+     * Server mode, request just received.
+     */
+    protected void processReceivedHeaders() throws IOException {
+        if (!httpCh.normalize(getMsgBytes().url())) {
+            httpCh.getResponse().setStatus(400);
+            httpCh.abort("Error normalizing url " + 
+                    getMsgBytes().url());
+            return;                
+        }
+        
+        method().set(getMsgBytes().method()); 
+        requestURI().set(getMsgBytes().url());
+        queryString().set(getMsgBytes().query());
+        protocol().set(getMsgBytes().protocol());
+        
+        processMimeHeaders();
+
+        // URL decode and normalize
+        decodedUri.append(getMsgBytes().url());
+        getURLDecoder().urlDecode(decodedUri, false); 
+        
+        // Need to normalize again - %decoding may decode /
+        if (!httpCh.normalize(decodedUri)) {
+            httpCh.getResponse().setStatus(400);
+            httpCh.abort("Invalid decoded uri " + decodedUri);
+            return;                
+        }
+        decodedURI().set(decodedUri);
+
+        httpCh.processProtocol();
+
+        // default response protocol
+        httpCh.getResponse().protocol().set(getMsgBytes().protocol());            
+
+        // requested connection:close/keepAlive and proto
+        httpCh.processConnectionHeader(getMimeHeaders());
+
+        httpCh.processExpectation();
+
+        httpCh.receiveBody.processContentDelimitation();
+        // Spec: 
+        // The presence of a message-body in a request is signaled by the 
+        // inclusion of a Content-Length or Transfer-Encoding header field in 
+        // the request's message-headers
+        // Server should read - but ignore
+
+        httpCh.receiveBody.noBody = !httpCh.receiveBody.isContentDelimited();
+
+        httpCh.receiveBody.updateCloseOnEnd();
+
+        /*
+         * The presence of a message-body in a request is signaled by the 
+         * inclusion of a Content-Length or Transfer-Encoding header field in 
+         * the request's message-headers. A message-body MUST NOT be included 
+         * in a request if the specification of the request method 
+         * (section 5.1.1) does not allow sending an entity-body in requests. 
+         * A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.
+         */
+        if (!httpCh.receiveBody.isContentDelimited()) {
+            // No body
+            httpCh.getIn().close();
+        } 
+
+        CBuffer valueMB = getMimeHeaders().getHeader("host");
+        // Check host header
+//        if (httpCh.http11 && (valueMB == null)) {
+//            httpCh.error = true;
+//            // 400 - Bad request
+//            httpCh.getResponse().setStatus(400);
+//        }
+    }
+
+    
+    public boolean hasBody() {
+        return chunked || contentLength >= 0; 
+    }
+
+    /**
+     * Convert (if necessary) and return the absolute URL that represents the
+     * resource referenced by this possibly relative URL.  If this URL is
+     * already absolute, return it unchanged.
+     *
+     * @param location URL to be (possibly) converted and then returned
+     *
+     * @exception IllegalArgumentException if a MalformedURLException is
+     *  thrown when converting the relative URL to an absolute one
+     */
+    public void toAbsolute(String location, CBuffer cb) {
+
+        cb.recycle();
+        if (location == null)
+            return;
+
+        boolean leadingSlash = location.startsWith("/");
+        if (leadingSlash || !hasScheme(location)) {
+
+            String scheme = getScheme();
+            String name = serverName().toString();
+            int port = getServerPort();
+
+            cb.append(scheme);                
+            cb.append("://", 0, 3);
+            cb.append(name);
+            if ((scheme.equals("http") && port != 80)
+                    || (scheme.equals("https") && port != 443)) {
+                cb.append(':');
+                String portS = port + "";
+                cb.append(portS);
+            }
+            if (!leadingSlash) {
+                String relativePath = decodedURI().toString();
+                int pos = relativePath.lastIndexOf('/');
+                relativePath = relativePath.substring(0, pos);
+
+                //String encodedURI = null;
+                urlEncoding.urlEncode(relativePath,  cb, charEncoder);
+                //encodedURI = urlEncoder.encodeURL(relativePath);
+                //redirectURLCC.append(encodedURI, 0, encodedURI.length());
+                cb.append('/');
+            }
+
+            cb.append(location);
+        } else {
+            cb.append(location);
+        }
+
+    }    
+    
+    /**
+     * Determine if a URI string has a <code>scheme</code> component.
+     */
+    public static boolean hasScheme(String uri) {
+        int len = uri.length();
+        for(int i=0; i < len ; i++) {
+            char c = uri.charAt(i);
+            if(c == ':') {
+                return i > 0;
+            } else if(!isSchemeChar(c)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determine if the character is allowed in the scheme of a URI.
+     * See RFC 2396, Section 3.1
+     */
+    private static boolean isSchemeChar(char c) {
+        return Character.isLetterOrDigit(c) ||
+            c == '+' || c == '-' || c == '.';
+    }
+    
+    public IOWriter getCharEncoder() {
+        return charEncoder;
+    }
+
+    public IOReader getCharDecoder() {
+        return charDecoder;
+    }
+    
+    public UrlEncoding getUrlEncoding() {
+        return urlEncoding;
+    }
+    
+    public BBuffer toBytes(CBuffer cb, BBuffer bb) {
+        if (bb == null) {
+            bb = BBuffer.allocate(cb.length());
+        }
+        getCharEncoder().encodeAll(cb, bb, "UTF-8");
+        return bb;
+    }
+
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java?rev=884412&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java Thu Nov 26 06:41:00 2009
@@ -0,0 +1,283 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.IOBuffer;
+
+public class HttpResponse extends HttpMessage {
+
+    // will not be recycled
+    public Object nativeResponse;
+
+    protected CBuffer message = CBuffer.newInstance();
+    
+    int status = -1;
+
+    HttpResponse(HttpChannel httpCh) {
+        super(httpCh);
+    }
+
+    public void recycle() {
+        super.recycle();
+        message.recycle();
+        status = -1;
+    }
+    
+    public void setMessage(String s) {
+        message.set(filter(s));
+    }
+    
+    public String getMessage() {
+        return message.toString();
+    }
+    
+    public CBuffer getMessageBuffer() {
+        return message;
+    }
+    
+    byte[] S_200 = new byte[] { '2', '0', '0' };
+    
+    public void setStatus(int i) {
+        status = i;
+    }
+    
+    public int getStatus() {
+        if (status >= 0) {
+            return status;
+        }
+        if (getMsgBytes().status().isNull()) {
+            status = 200;
+        } else {
+            try {
+                status = getMsgBytes().status().getInt();
+            } catch(NumberFormatException ex) {
+                status = 500;
+                httpCh.log.severe("Invalid status " + getMsgBytes().status());
+            }
+        }
+        return status;
+    }
+
+    public void sendHead() throws IOException {
+        httpCh.sendHeaders();
+    }
+
+    /** 
+     * Convert the response to bytes, ready to send.
+     */
+    public void serialize(IOBuffer rawSendBuffers2) throws IOException {
+        
+        rawSendBuffers2.append(protocol()).append(' ');
+        String status = Integer.toString(getStatus());   
+        rawSendBuffers2.append(status).append(' ');
+        if (getMessageBuffer().length() > 0) {
+            rawSendBuffers2.append(getMessage());
+        } else {
+            rawSendBuffers2
+                .append(getMessage(getStatus()));
+        }
+        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+        // Headers
+        super.serializeHeaders(rawSendBuffers2);
+    }
+
+    public HttpRequest getRequest() {
+        return getHttpChannel().getRequest();
+    }
+    
+    // Http client mode.
+    protected void processReceivedHeaders() throws IOException {
+        protocol().set(getMsgBytes().protocol());                
+        message.set(getMsgBytes().message());
+        processMimeHeaders();
+        
+        
+        // TODO: if protocol == 1.0 and we requested 1.1, downgrade getHttpChannel().pro
+        int status = 500;
+        try {
+            status = getStatus();
+        } catch (Throwable t) {
+            getHttpChannel().log.warning("Invalid status " + getMsgBytes().status() + " " + getMessage());
+        }
+        HttpBody body = (HttpBody) getBody();
+        body.noBody = !hasBody();
+
+        // Will parse 'connection:close', set close on end
+        getHttpChannel().processConnectionHeader(getMimeHeaders());
+        
+        body.processContentDelimitation();
+        
+        if (body.statusDropsConnection(status)) {
+            getHttpChannel().closeStreamOnEnd("response status drops connection");
+        }
+        
+        if (body.isDone()) {
+            body.close();
+        }
+
+        if (!body.isContentDelimited()) {
+            getHttpChannel().closeStreamOnEnd("not content delimited");
+        }
+        
+        
+    }
+
+    /**
+     * All responses to the HEAD request method MUST NOT include a 
+     * message-body, even though the presence of entity- header fields might
+     *  lead one to believe they do. All 1xx (informational), 204 (no content)
+     *  , and 304 (not modified) responses MUST NOT include a message-body. All 
+     *  other responses do include a message-body, although it MAY be of zero 
+     *  length.
+     */
+    public boolean hasBody() {
+        if (httpCh.getRequest().method().equals("HEAD")) {
+            return false;
+        }
+        if (status >= 100 && status < 200) {
+            return false;
+        }
+        // what about (status == 205) ?
+        if ((status == 204) 
+                || (status == 304)) {
+            return false;
+        }
+        return true;
+    }
+    
+    /** Get the status string associated with a status code.
+     *  No I18N - return the messages defined in the HTTP spec.
+     *  ( the user isn't supposed to see them, this is the last
+     *  thing to translate)
+     *
+     *  Common messages are cached.
+     *
+     */
+    private BBucket getMessage( int status ) {
+        // method from Response.
+
+        // Does HTTP requires/allow international messages or
+        // are pre-defined? The user doesn't see them most of the time
+        switch( status ) {
+        case 200:
+            return st_200;
+        case 302:
+            return st_302;
+        case 400:
+            return st_400;
+        case 404:
+            return st_404;
+        }
+        return stats.get(status);
+    }
+    
+    
+    static BBucket st_200 = BBuffer.wrapper("OK");
+    static BBucket st_302= BBuffer.wrapper("Moved Temporarily");
+    static BBucket st_400= BBuffer.wrapper("Bad Request");
+    static BBucket st_404= BBuffer.wrapper("Not Found");
+
+    static HashMap<Integer,BBucket> stats = new HashMap<Integer, BBucket>();
+    private static void addStatus(int stat, String msg) {
+        stats.put(stat, BBuffer.wrapper(msg));
+    }
+    
+    static {
+        addStatus(100, "Continue");
+        addStatus(101, "Switching Protocols");
+        addStatus(200, "OK");
+        addStatus(201, "Created");
+        addStatus(202, "Accepted");
+        addStatus(203, "Non-Authoritative Information");
+        addStatus(204, "No Content");
+        addStatus(205, "Reset Content");
+        addStatus(206, "Partial Content");
+        addStatus(207, "Multi-Status");
+        addStatus(300, "Multiple Choices");
+        addStatus(301, "Moved Permanently");
+        addStatus(302, "Moved Temporarily");
+        addStatus(303, "See Other");
+        addStatus(304, "Not Modified");
+        addStatus(305, "Use Proxy");
+        addStatus(307, "Temporary Redirect");
+        addStatus(400, "Bad Request");
+        addStatus(401, "Unauthorized");
+        addStatus(402, "Payment Required");
+        addStatus(403, "Forbidden");
+        addStatus(404, "Not Found");
+        addStatus(405, "Method Not Allowed");
+        addStatus(406, "Not Acceptable");
+        addStatus(407, "Proxy Authentication Required");
+        addStatus(408, "Request Timeout");
+        addStatus(409, "Conflict");
+        addStatus(410, "Gone");
+        addStatus(411, "Length Required");
+        addStatus(412, "Precondition Failed");
+        addStatus(413, "Request Entity Too Large");
+        addStatus(414, "Request-URI Too Long");
+        addStatus(415, "Unsupported Media Type");
+        addStatus(416, "Requested Range Not Satisfiable");
+        addStatus(417, "Expectation Failed");
+        addStatus(422, "Unprocessable Entity");
+        addStatus(423, "Locked");
+        addStatus(424, "Failed Dependency");
+        addStatus(500, "Internal Server Error");
+        addStatus(501, "Not Implemented");
+        addStatus(502, "Bad Gateway");
+        addStatus(503, "Service Unavailable");
+        addStatus(504, "Gateway Timeout");
+        addStatus(505, "HTTP Version Not Supported");
+        addStatus(507, "Insufficient Storage");
+        
+    }
+    
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    private static String filter(String message) {
+
+        if (message == null)
+            return (null);
+        if (message.indexOf('<') < 0 &&
+                message.indexOf('>') < 0 &&
+                message.indexOf('&') < 0 &&
+                message.indexOf('"') < 0) {
+            return message;
+        }
+        
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+
+        StringBuffer result = new StringBuffer(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+    }
+    
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java?rev=884412&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java Thu Nov 26 06:41:00 2009
@@ -0,0 +1,313 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.tomcat.lite.io.IOOutputStream;
+import org.apache.tomcat.lite.io.IOWriter;
+
+/**
+ * Implement character translation and buffering.
+ * 
+ * The actual buffering happens in the IOBuffer - we translate the 
+ * chars as soon as we get them. 
+ * 
+ * For servlet compat you can set a buffer size and a flush() will happen
+ * when the number of chars have been written. Note that writes at a lower 
+ * layer can be done and are not counted. 
+ * 
+ * @author Costin Manolache
+ */
+public class HttpWriter extends Writer {
+
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+    // ----------------------------------------------------- Instance Variables
+    HttpMessage message;
+
+    /**
+     * The byte buffer.
+     */
+    protected IOOutputStream bb;
+
+    int bufferSize = DEFAULT_BUFFER_SIZE;
+    
+    /**
+     * Number of chars written.
+     */
+    protected int wSinceFlush = 0;
+
+
+    /**
+     * Flag which indicates if the output buffer is closed.
+     */
+    protected boolean closed = false;
+
+    /**
+     * Encoding to use. 
+     * TODO: isn't it redundant ? enc, gotEnc, conv plus the enc in the bb
+     */
+    protected String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    protected boolean gotEnc = false;
+
+
+    /**
+     * List of encoders. The writer is reused - the encoder mapping 
+     * avoids creating expensive objects. In future it'll contain nio.Charsets
+     */
+    //protected Map<String, C2BConverter> encoders = new HashMap();
+
+
+    /**
+     * Current char to byte converter. TODO: replace with Charset
+     */
+    private IOWriter conv;
+
+    /**
+     * Suspended flag. All output bytes will be swallowed if this is true.
+     */
+    protected boolean suspended = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     * @param out 
+     */
+    public HttpWriter(HttpMessage message, IOOutputStream out,
+            IOWriter conv) {
+        this.message = message;
+        bb = out;
+        this.conv = conv;
+    }
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Is the response output suspended ?
+     * 
+     * @return suspended flag value
+     */
+    public boolean isSuspended() {
+        return this.suspended;
+    }
+
+
+    /**
+     * Set the suspended flag.
+     * 
+     * @param suspended New suspended flag value
+     */
+    public void setSuspended(boolean suspended) {
+        this.suspended = suspended;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        wSinceFlush = 0;
+        bb.recycle(); 
+        closed = false;
+        suspended = false;
+        
+//        if (conv != null) {
+//            conv.recycle();
+//        }
+        
+        gotEnc = false;
+        enc = null;
+    }
+
+    public void close()
+        throws IOException {
+
+        if (closed)
+            return;
+        if (suspended)
+            return;
+
+        push();
+        closed = true;
+        
+        bb.close();
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public void flush()
+            throws IOException {
+        push();
+        bb.flush(); // will send the data
+        wSinceFlush = 0;
+    }
+
+    /**
+     * Flush chars to the byte buffer.
+     */
+    public void push()
+        throws IOException {
+
+        if (suspended)
+            return;
+        getConv().push();
+        
+    }
+
+
+    private void updateSize(int cnt) throws IOException {
+        wSinceFlush += cnt;
+        if (wSinceFlush > bufferSize) {
+            flush();
+        }
+    }
+
+    public void write(int c)
+            throws IOException {
+        if (suspended)
+            return;
+        getConv().write(c);
+        updateSize(1);
+    }
+
+
+    public void write(char c[])
+            throws IOException {
+        write(c, 0, c.length);
+    }
+
+
+    public void write(char c[], int off, int len)
+            throws IOException {
+        if (suspended)
+            return;
+        getConv().write(c, off, len);
+        updateSize(len);
+    }
+
+
+    public void write(StringBuffer sb)
+            throws IOException {
+        if (suspended)
+            return;
+        int len = sb.length();
+        getConv().write(sb.toString());
+        updateSize(len);
+    }
+
+
+    /**
+     * Append a string to the buffer
+     */
+    public void write(String s, int off, int len)
+        throws IOException {
+        if (suspended)
+            return;
+        if (s==null)
+            s="null";
+        getConv().write( s, off, len );
+        updateSize(len);
+    }
+
+
+    public void write(String s)
+            throws IOException {
+        if (s==null)
+            s="null";
+        write(s, 0, s.length());
+    } 
+
+    public void println() throws IOException {
+        write("\n");
+    }
+
+    public void println(String s) throws IOException {
+        write(s);
+        write("\n");
+    }
+
+    public void print(String s) throws IOException {
+        write(s);
+    }
+
+    public void checkConverter() 
+            throws IOException {
+//        if (gotEnc) {
+//            return;
+//        }
+//        if (enc == null) {
+//            enc = message.getCharacterEncoding();
+//        }
+//
+//        gotEnc = true;
+//        if (enc == null)
+//            enc = DEFAULT_ENCODING;
+//        conv = (C2BConverter) encoders.get(enc);
+//        
+//        if (conv == null) {
+//            conv = C2BConverter.newConverter(message.getBodyOutputStream(), 
+//                    enc);
+//            encoders.put(enc, conv);
+//
+//        }
+    }
+
+    public int getWrittenSinceFlush() {
+        return wSinceFlush;
+    }
+
+
+    public void setBufferSize(int size) {
+        if (size > bufferSize) {
+            bufferSize = size;
+        }
+    }
+
+    /**
+     *  Clear any data that was buffered.
+     */
+    public void reset() {
+        if (conv != null) { 
+            conv.recycle();
+        }
+        wSinceFlush = 0;
+        gotEnc = false;
+        enc = null;
+        bb.reset();
+    }
+
+
+    public int getBufferSize() {
+        return bufferSize;
+    }
+
+    protected IOWriter getConv() throws IOException {
+        checkConverter();
+        return conv;
+    }
+
+    public void println(CharSequence key) throws IOException {
+        // TODO: direct
+        println(key.toString());
+    }
+
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java?rev=884412&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java Thu Nov 26 06:41:00 2009
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.io.CBuffer;
+
+
+/**
+ * Mapping data.
+ *
+ * @author Remy Maucherat
+ */
+public class MappingData {
+
+    public Object context = null; // ServletContextImpl
+    
+    public BaseMapper.ContextMapping contextMap;
+
+    public BaseMapper.ServiceMapping service = null;
+
+    public CBuffer contextPath = CBuffer.newInstance();
+    public CBuffer requestPath = CBuffer.newInstance();
+    public CBuffer wrapperPath = CBuffer.newInstance();
+    public CBuffer pathInfo = CBuffer.newInstance();
+
+    public CBuffer redirectPath = CBuffer.newInstance();
+
+    // Extension
+    CBuffer ext = CBuffer.newInstance();
+    CBuffer tmpPrefix = CBuffer.newInstance();
+    
+    // Excluding context path, with a '/' added if needed
+    CBuffer tmpServletPath = CBuffer.newInstance();
+
+    // Excluding context path, with a '/' added if needed
+    CBuffer tmpWelcome = CBuffer.newInstance();
+
+    public void recycle() {
+        service = null; 
+        context = null;
+        pathInfo.recycle();
+        requestPath.recycle();
+        wrapperPath.recycle();
+        contextPath.recycle();
+        redirectPath.recycle();
+        contextMap = null;
+    }
+
+
+    public Object getServiceObject() {
+        return service == null ? null : service.object;
+    }
+
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java?rev=884412&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java Thu Nov 26 06:41:00 2009
@@ -0,0 +1,358 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.tomcat.lite.http;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBucket;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.util.buf.CharChunk;
+
+/**
+ * Map used to represent headers and parameters ( could be used 
+ * for cookies too )
+ * 
+ * It'll avoid garbage collection, like original tomcat classes, 
+ * by converting to chars and strings late.
+ * 
+ * Not thread safe.
+ */
+public class MultiMap {
+
+    public static class Entry {
+        // Wrappers from the head message bytes.
+        BBuffer nameB; 
+        BBuffer valueB;
+        
+        CBuffer key = CBuffer.newInstance();
+        private CBuffer value = CBuffer.newInstance();
+        
+        /** 
+         * For the first entry with a given name: list of all
+         * other entries, including this one, with same name.
+         * 
+         * For second or more: empty list
+         */
+        public List<Entry> values = new ArrayList<Entry>(); 
+    
+        public void recycle() {
+            key.recycle();
+            value.recycle();
+            //next=null;
+            nameB = null;
+            valueB = null;
+            values.clear();
+        }
+    
+        public CBuffer getName() {
+            if (key.length() == 0 && nameB != null) {
+                key.set(nameB);
+            }
+            return key;
+        }
+    
+        public CBuffer getValue() {
+            if (value.length() == 0 && valueB != null) {
+                value.set(valueB);
+            }
+            return value;
+        }
+
+        /** Important - used by values iterator, returns strings 
+         * from each entry
+         */
+        public String toString() {
+            return getValue().toString();
+        }
+        
+    }
+
+    // active entries
+    protected int count;
+
+    // The key will be converted to lower case
+    boolean toLower = false;
+    
+    // Some may be inactive - up to count.
+    protected List<Entry> entries = new ArrayList<Entry>();
+    
+    // 2 options: convert all header/param names to String
+    // or use a temp CBuffer to map
+    Map<CBuffer, Entry> map = 
+        new HashMap<CBuffer, Entry>();
+    
+    public void recycle() {
+        for (int i = 0; i < count; i++) {
+            Entry entry = entries.get(i);
+            entry.recycle();
+        }
+        count = 0;
+        map.clear();
+    }
+
+    // ----------- Mutations ------------------------
+
+    protected Entry newEntry()  {
+        return new Entry();
+    }
+    
+    /**
+     * Adds a partially constructed field entry.
+     * Updates count - but will not affect the map.
+     */
+    private Entry getEntryForAdd() {
+        Entry entry;
+        if (count >= entries.size()) {
+            entry = newEntry();
+            entries.add(entry);
+        } else {
+            entry = entries.get(count);
+        }
+        count++;
+        return entry;
+    }
+    
+
+    /** Create a new named header , return the CBuffer
+     *  container for the new value
+     */
+   public Entry addEntry(CharSequence name ) {
+       Entry mh = getEntryForAdd();
+       mh.getName().append(name);
+       if (toLower) {
+           mh.getName().toLower();
+       }
+       updateMap(mh);
+       return mh;
+   }
+
+   /** Create a new named header , return the CBuffer
+    *  container for the new value
+    */
+   public Entry addEntry(BBuffer name ) {
+       Entry mh = getEntryForAdd();
+       mh.nameB = name;
+       if (toLower) {
+           mh.getName().toLower();
+       }       
+       updateMap(mh);
+       
+       return mh;
+   }
+
+   private void updateMap(Entry mh) {
+       Entry topEntry = map.get(mh.getName());
+       
+       if (topEntry == null) {
+           map.put(mh.getName(), mh);
+           mh.values.add(mh);            
+       } else {
+           topEntry.values.add(mh);
+       }
+   }
+
+
+
+    public void remove(CharSequence key) {
+        CBucket ckey = key(key);
+        Entry entry = getEntry(ckey);
+        if (entry != null) {
+            map.remove(ckey);
+            
+            for (int i = count - 1; i >= 0; i--) {
+                entry = entries.get(i);
+                if (entry.getName().equals(key)) {
+                    entry.recycle();
+                    entries.remove(i);
+                    count--;
+                }
+            }
+        }            
+    }
+
+    // --------------- Key-based access --------------
+    CBuffer tmpKey = CBuffer.newInstance();
+    
+    /**
+     * Finds and returns a header field with the given name.  If no such
+     * field exists, null is returned.  If more than one such field is
+     * in the header, an arbitrary one is returned.
+     */
+    public CBuffer getHeader(String name) {
+        for (int i = 0; i < count; i++) {
+            if (entries.get(i).getName().equalsIgnoreCase(name)) {
+                return entries.get(i).getValue();
+            }
+        }
+        return null;
+    }
+
+    private CBucket key(CharSequence key) {
+        if (key instanceof CBucket) {
+            CBucket res = (CBucket) key;
+            if (!toLower || !res.hasUpper()) {
+                return res;
+            }
+        }
+        tmpKey.recycle();
+        tmpKey.append(key);
+        if (toLower) {
+            tmpKey.toLower();
+        }        
+        return tmpKey;
+    }
+    
+    public Entry getEntry(CharSequence key) {
+        Entry entry = map.get(key(key));
+        return entry;
+    }
+
+    public Entry getEntry(CBucket buf) {
+        // lowercase ?
+        Entry entry = map.get(buf);
+        return entry;
+    }
+
+    public Enumeration<String> names() {
+        return new IteratorEnumerator(map.keySet().iterator());
+    }    
+
+    // ----------- Index access --------------
+    
+    /**
+     *  Number of entries ( including those with same key
+     * 
+     * @return
+     */
+    public int size() {
+        return count;
+    }
+
+
+    public CharSequence getKey(int idx) {
+        return entries.get(idx).key;
+    }
+
+    public Entry getEntry(int idx) {
+        return entries.get(idx);
+    }
+
+    /**
+     * Returns the Nth header name, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public CBuffer getName(int n) {
+        return n < count ? entries.get(n).getName() : null;
+    }
+
+    /**
+     * Returns the Nth header value, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public CBuffer getValue(int n) {
+        return n >= 0 && n < count ? entries.get(n).getValue() : null;
+    }
+
+    // ----------- Helpers --------------
+    public void add(CharSequence key, CharSequence value) {
+        Entry mh = addEntry(key);
+        mh.value.append(value);
+    }
+    
+    /** Create a new named header , return the CBuffer
+     * container for the new value
+     */
+    public CBuffer addValue( String name ) {
+        return addEntry(name).getValue();
+    }
+
+     public Entry setEntry( String name ) {
+         remove(name);
+         return addEntry(name);
+     }
+
+     public void set(CharSequence key, CharSequence value) {
+         remove(key);
+         add(key, value);
+     }
+     
+     public CBuffer setValue( String name ) {
+         remove(name);
+         return addValue(name);
+     }
+
+     public CBuffer get(CharSequence key) {
+         Entry entry = getEntry(key);
+         return (entry == null) ? null : entry.value;
+     }
+
+     public String getString(CharSequence key) {
+         Entry entry = getEntry(key);
+         return (entry == null) ? null : entry.value.toString();
+     }
+     
+    
+    // -------------- support classes ----------------
+    
+    public static class IteratorEnumerator implements Enumeration<String> {
+        private final Iterator keyI;
+    
+        public IteratorEnumerator(Iterator iterator) {
+            this.keyI = iterator;
+        }
+    
+        
+        public boolean hasMoreElements() {
+            return keyI.hasNext();
+        }
+    
+        
+        public String nextElement() {
+            return keyI.next().toString();
+        }
+
+    }
+
+    public static final Enumeration<String> EMPTY = 
+        new Enumeration<String>() {
+
+            @Override
+            public boolean hasMoreElements() {
+                return false;
+            }
+
+            @Override
+            public String nextElement() {
+                return null;
+            }
+        
+    };
+
+    public MultiMap insensitive() {
+        toLower = true;
+        return this;
+    }
+
+    
+}

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org