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("<");
+ break;
+ case '>':
+ result.append(">");
+ break;
+ case '&':
+ result.append("&");
+ break;
+ case '"':
+ result.append(""");
+ 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